home *** CD-ROM | disk | FTP | other *** search
/ PC Format (PL) 2008 February / PC_Format_022008.iso / Internet / Mozilla Thunderbird wtyczki / lightning-0.7-tb-win.xpi / components / calStorageCalendar.js < prev    next >
Encoding:
Text File  |  2007-09-22  |  87.2 KB  |  2,336 lines

  1. /* -*- Mode: javascript; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
  2. /* ***** BEGIN LICENSE BLOCK *****
  3.  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
  4.  *
  5.  * The contents of this file are subject to the Mozilla Public License Version
  6.  * 1.1 (the "License"); you may not use this file except in compliance with
  7.  * the License. You may obtain a copy of the License at
  8.  * http://www.mozilla.org/MPL/
  9.  *
  10.  * Software distributed under the License is distributed on an "AS IS" basis,
  11.  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  12.  * for the specific language governing rights and limitations under the
  13.  * License.
  14.  *
  15.  * The Original Code is Oracle Corporation code.
  16.  *
  17.  * The Initial Developer of the Original Code is
  18.  *  Oracle Corporation
  19.  * Portions created by the Initial Developer are Copyright (C) 2005, 2006
  20.  * the Initial Developer. All Rights Reserved.
  21.  *
  22.  * Contributor(s):
  23.  *   Vladimir Vukicevic <vladimir.vukicevic@oracle.com>
  24.  *   Joey Minta <jminta@gmail.com>
  25.  *   Dan Mosedale <dan.mosedale@oracle.com>
  26.  *   Thomas Benisch <thomas.benisch@sun.com>
  27.  *   Matthew Willis <lilmatt@mozilla.com>
  28.  *   Philipp Kewisch <mozilla@kewis.ch>
  29.  *
  30.  * Alternatively, the contents of this file may be used under the terms of
  31.  * either the GNU General Public License Version 2 or later (the "GPL"), or
  32.  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  33.  * in which case the provisions of the GPL or the LGPL are applicable instead
  34.  * of those above. If you wish to allow use of your version of this file only
  35.  * under the terms of either the GPL or the LGPL, and not to allow others to
  36.  * use your version of this file under the terms of the MPL, indicate your
  37.  * decision by deleting the provisions above and replace them with the notice
  38.  * and other provisions required by the GPL or the LGPL. If you do not delete
  39.  * the provisions above, a recipient may use your version of this file under
  40.  * the terms of any one of the MPL, the GPL or the LGPL.
  41.  *
  42.  * ***** END LICENSE BLOCK ***** */
  43.  
  44. //
  45. // calStorageCalendar.js
  46. //
  47.  
  48. const kStorageServiceContractID = "@mozilla.org/storage/service;1";
  49. const kStorageServiceIID = Components.interfaces.mozIStorageService;
  50.  
  51. const kCalICalendar = Components.interfaces.calICalendar;
  52.  
  53. const kCalAttendeeContractID = "@mozilla.org/calendar/attendee;1";
  54. const kCalIAttendee = Components.interfaces.calIAttendee;
  55. var CalAttendee;
  56.  
  57. const kCalRecurrenceInfoContractID = "@mozilla.org/calendar/recurrence-info;1";
  58. const kCalIRecurrenceInfo = Components.interfaces.calIRecurrenceInfo;
  59. var CalRecurrenceInfo;
  60.  
  61. const kCalRecurrenceRuleContractID = "@mozilla.org/calendar/recurrence-rule;1";
  62. const kCalIRecurrenceRule = Components.interfaces.calIRecurrenceRule;
  63. var CalRecurrenceRule;
  64.  
  65. const kCalRecurrenceDateSetContractID = "@mozilla.org/calendar/recurrence-date-set;1";
  66. const kCalIRecurrenceDateSet = Components.interfaces.calIRecurrenceDateSet;
  67. var CalRecurrenceDateSet;
  68.  
  69. const kCalRecurrenceDateContractID = "@mozilla.org/calendar/recurrence-date;1";
  70. const kCalIRecurrenceDate = Components.interfaces.calIRecurrenceDate;
  71. var CalRecurrenceDate;
  72.  
  73. const kMozStorageStatementWrapperContractID = "@mozilla.org/storage/statement-wrapper;1";
  74. const kMozStorageStatementWrapperIID = Components.interfaces.mozIStorageStatementWrapper;
  75. var MozStorageStatementWrapper;
  76.  
  77. if (!kMozStorageStatementWrapperIID) {
  78.     dump("*** mozStorage not available, calendar/storage provider will not function\n");
  79. }
  80.  
  81. const CAL_ITEM_TYPE_EVENT = 0;
  82. const CAL_ITEM_TYPE_TODO = 1;
  83.  
  84. // bitmasks
  85. const CAL_ITEM_FLAG_PRIVATE = 1;
  86. const CAL_ITEM_FLAG_HAS_ATTENDEES = 2;
  87. const CAL_ITEM_FLAG_HAS_PROPERTIES = 4;
  88. const CAL_ITEM_FLAG_EVENT_ALLDAY = 8;
  89. const CAL_ITEM_FLAG_HAS_RECURRENCE = 16;
  90. const CAL_ITEM_FLAG_HAS_EXCEPTIONS = 32;
  91.  
  92. const USECS_PER_SECOND = 1000000;
  93.  
  94. function initCalStorageCalendarComponent() {
  95.     CalAttendee = new Components.Constructor(kCalAttendeeContractID, kCalIAttendee);
  96.     CalRecurrenceInfo = new Components.Constructor(kCalRecurrenceInfoContractID, kCalIRecurrenceInfo);
  97.     CalRecurrenceRule = new Components.Constructor(kCalRecurrenceRuleContractID, kCalIRecurrenceRule);
  98.     CalRecurrenceDateSet = new Components.Constructor(kCalRecurrenceDateSetContractID, kCalIRecurrenceDateSet);
  99.     CalRecurrenceDate = new Components.Constructor(kCalRecurrenceDateContractID, kCalIRecurrenceDate);
  100.     MozStorageStatementWrapper = new Components.Constructor(kMozStorageStatementWrapperContractID, kMozStorageStatementWrapperIID);
  101. }
  102.  
  103. //
  104. // Storage helpers
  105. //
  106.  
  107. function createStatement (dbconn, sql) {
  108.     try {
  109.         var stmt = dbconn.createStatement(sql);
  110.         var wrapper = MozStorageStatementWrapper();
  111.         wrapper.initialize(stmt);
  112.         return wrapper;
  113.     } catch (e) {
  114.         Components.utils.reportError(
  115.             "mozStorage exception: createStatement failed, statement: '" + 
  116.             sql + "', error: '" + dbconn.lastErrorString + "'");
  117.     }
  118.  
  119.     return null;
  120. }
  121.  
  122. function textToDate(d) {
  123.     var dval;
  124.     var tz = "UTC";
  125.  
  126.     if (d[0] == 'Z') {
  127.         var strs = d.substr(2).split(":");
  128.         dval = parseInt(strs[0]);
  129.         tz = strs[1].replace(/%:/g, ":").replace(/%%/g, "%");
  130.     } else {
  131.         dval = parseInt(d.substr(2));
  132.     }
  133.  
  134.     var date;
  135.     if (d[0] == 'U' || d[0] == 'Z') {
  136.         date = newDateTime(dval, tz);
  137.     } else if (d[0] == 'L') {
  138.         // is local time
  139.         date = newDateTime(dval, "floating");
  140.     }
  141.  
  142.     if (d[1] == 'D')
  143.         date.isDate = true;
  144.     return date;
  145. }
  146.  
  147. function dateToText(d) {
  148.     var datestr;
  149.     var tz = null;
  150.     if (d.timezone != "floating") {
  151.         if (d.timezone == "UTC") {
  152.             datestr = "U";
  153.         } else {
  154.             datestr = "Z";
  155.             tz = d.timezone;
  156.         }
  157.     } else {
  158.         datestr = "L";
  159.     }
  160.  
  161.     if (d.isDate) {
  162.         datestr += "D";
  163.     } else {
  164.         datestr += "T";
  165.     }
  166.  
  167.     datestr += d.nativeTime;
  168.  
  169.     if (tz) {
  170.         // replace '%' with '%%', then replace ':' with '%:'
  171.         tz = tz.replace(/%/g, "%%");
  172.         tz = tz.replace(/:/g, "%:");
  173.         datestr += ":" + tz;
  174.     }
  175.     return datestr;
  176. }
  177.  
  178. // 
  179. // other helpers
  180. //
  181.  
  182. function newDateTime(aNativeTime, aTimezone) {
  183.     var t = createDateTime();
  184.     t.nativeTime = aNativeTime;
  185.     if (aTimezone && aTimezone != "floating") {
  186.         t = t.getInTimezone(aTimezone);
  187.     } else {
  188.         t.timezone = "floating";
  189.     }
  190.  
  191.     return t;
  192. }
  193.  
  194. //
  195. // calStorageCalendar
  196. //
  197.  
  198. function calStorageCalendar() {
  199.     this.wrappedJSObject = this;
  200.     this.mObservers = new calListenerBag(Components.interfaces.calIObserver);
  201.     this.mItemCache = new Array();
  202. }
  203.  
  204. calStorageCalendar.prototype = {
  205.     //
  206.     // private members
  207.     //
  208.     mDB: null,
  209.     mDBTwo: null,
  210.     mCalId: 0,
  211.  
  212.     //
  213.     // nsISupports interface
  214.     // 
  215.     QueryInterface: function (aIID) {
  216.         if (!aIID.equals(Components.interfaces.nsISupports) &&
  217.             !aIID.equals(Components.interfaces.calICalendarProvider) &&
  218.             !aIID.equals(Components.interfaces.calICalendar))
  219.         {
  220.             throw Components.results.NS_ERROR_NO_INTERFACE;
  221.         }
  222.  
  223.         return this;
  224.     },
  225.  
  226.     //
  227.     // calICalendarProvider interface
  228.     //
  229.     get prefChromeOverlay() {
  230.         return null;
  231.     },
  232.  
  233.     get displayName() {
  234.         return calGetString("calendar", "storageName");
  235.     },
  236.  
  237.     createCalendar: function stor_createCal() {
  238.         throw NS_ERROR_NOT_IMPLEMENTED;
  239.     },
  240.  
  241.     deleteCalendar: function stor_deleteCal(cal, listener) {
  242.         cal = cal.wrappedJSObject;
  243.  
  244.         for (var i in this.mDeleteEventExtras) {
  245.             this.mDeleteEventExtras[i].params.cal_id = cal.mCalId;
  246.             this.mDeleteEventExtras[i].execute();
  247.         }
  248.  
  249.         for (var i in this.mDeleteTodoExtras) {
  250.             this.mDeleteTodoExtras[i].params.cal_id = cal.mCalId;
  251.             this.mDeleteTodoExtras[i].execute();
  252.         }
  253.  
  254.         this.mDeleteAllEvents.params.cal_id = cal.mCalId;
  255.         this.mDeleteAllEvents.execute();
  256.  
  257.         this.mDeleteAllTodos.params.cal_id = cal.mCalId;
  258.         this.mDeleteAllTodos.execute();
  259.  
  260.         try {
  261.             listener.onDeleteCalendar(cal, Components.results.NS_OK, null);
  262.         } catch (ex) {
  263.         }
  264.     },
  265.  
  266.     //
  267.     // calICalendar interface
  268.     //
  269.     
  270.     // attribute AUTF8String id;
  271.     mID: null,
  272.     get id() {
  273.         return this.mID;
  274.     },
  275.     set id(id) {
  276.         if (this.mID)
  277.             throw Components.results.NS_ERROR_ALREADY_INITIALIZED;
  278.         return (this.mID = id);
  279.     },
  280.  
  281.     // attribute AUTF8String name;
  282.     get name() {
  283.         return getCalendarManager().getCalendarPref(this, "NAME");
  284.     },
  285.     set name(name) {
  286.         getCalendarManager().setCalendarPref(this, "NAME", name);
  287.     },
  288.     // readonly attribute AUTF8String type;
  289.     get type() { return "storage"; },
  290.  
  291.     mReadOnly: false,
  292.  
  293.     get readOnly() { 
  294.         return this.mReadOnly;
  295.     },
  296.     set readOnly(bool) {
  297.         this.mReadOnly = bool;
  298.     },
  299.  
  300.     get canRefresh() {
  301.         return false;
  302.     },
  303.  
  304.     mURI: null,
  305.     // attribute nsIURI uri;
  306.     get uri() { return this.mURI; },
  307.     set uri(aURI) {
  308.         // we can only load once
  309.         if (this.mURI)
  310.             throw Components.results.NS_ERROR_FAILURE;
  311.  
  312.         var id = 0;
  313.  
  314.         // check if there's a ?id=
  315.         var path = aURI.path;
  316.         var pos = path.indexOf("?id=");
  317.  
  318.         if (pos != -1) {
  319.             id = parseInt(path.substr(pos+4));
  320.             path = path.substr(0, pos);
  321.         }
  322.  
  323.         var dbService;
  324.         if (aURI.scheme == "file") {
  325.             var fileURL = aURI.QueryInterface(Components.interfaces.nsIFileURL);
  326.             if (!fileURL)
  327.                 throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
  328.  
  329.             // open the database
  330.             dbService = Components.classes[kStorageServiceContractID].getService(kStorageServiceIID);
  331.             this.mDB = dbService.openDatabase (fileURL.file);
  332.             this.mDBTwo = dbService.openDatabase (fileURL.file);
  333.         } else if (aURI.scheme == "moz-profile-calendar") {
  334.             dbService = Components.classes[kStorageServiceContractID].getService(kStorageServiceIID);
  335.         if ( "getProfileStorage" in dbService ) {
  336.           // 1.8 branch
  337.           this.mDB = dbService.getProfileStorage("profile");
  338.           this.mDBTwo = dbService.getProfileStorage("profile");
  339.         } else {
  340.           // trunk 
  341.           this.mDB = dbService.openSpecialDatabase("profile");
  342.           this.mDBTwo = dbService.openSpecialDatabase("profile");
  343.         }
  344.     }
  345.  
  346.         this.initDB();
  347.  
  348.         this.mCalId = id;
  349.         this.mURI = aURI;
  350.     },
  351.  
  352.     refresh: function() {
  353.         // no-op
  354.     },
  355.  
  356.     // attribute boolean suppressAlarms;
  357.     get suppressAlarms() { return false; },
  358.     set suppressAlarms(aSuppressAlarms) { throw Components.results.NS_ERROR_NOT_IMPLEMENTED; },
  359.  
  360.     get sendItipInvitations() { return true; },
  361.  
  362.     // void addObserver( in calIObserver observer );
  363.     addObserver: function (aObserver) {
  364.         this.mObservers.add(aObserver);
  365.     },
  366.  
  367.     // void removeObserver( in calIObserver observer );
  368.     removeObserver: function (aObserver) {
  369.         this.mObservers.remove(aObserver);
  370.     },
  371.  
  372.     // void addItem( in calIItemBase aItem, in calIOperationListener aListener );
  373.     addItem: function (aItem, aListener) {
  374.         var newItem = aItem.clone();
  375.         return this.adoptItem(newItem, aListener);
  376.     },
  377.  
  378.     // void adoptItem( in calIItemBase aItem, in calIOperationListener aListener );
  379.     adoptItem: function (aItem, aListener) {
  380.         if (this.readOnly) 
  381.             throw Components.interfaces.calIErrors.CAL_IS_READONLY;
  382.         // Ensure that we're looking at the base item
  383.         // if we were given an occurrence.  Later we can
  384.         // optimize this.
  385.         if (aItem.parentItem != aItem) {
  386.             aItem.parentItem.recurrenceInfo.modifyException(aItem);
  387.         }
  388.         aItem = aItem.parentItem;
  389.  
  390.         if (aItem.id == null) {
  391.             // is this an error?  Or should we generate an IID?
  392.             aItem.id = getUUID();
  393.         } else {
  394.             var olditem = this.getItemById(aItem.id);
  395.             if (olditem) {
  396.                 if (aListener)
  397.                     aListener.onOperationComplete (this,
  398.                                                    Components.interfaces.calIErrors.DUPLICATE_ID,
  399.                                                    aListener.ADD,
  400.                                                    aItem.id,
  401.                                                    "ID already exists for addItem");
  402.                 return;
  403.             }
  404.         }
  405.  
  406.         aItem.calendar = this;
  407.         aItem.generation = 1;
  408.         aItem.makeImmutable();
  409.  
  410.         this.flushItem (aItem, null);
  411.  
  412.         // notify the listener
  413.         if (aListener)
  414.             aListener.onOperationComplete (this,
  415.                                            Components.results.NS_OK,
  416.                                            aListener.ADD,
  417.                                            aItem.id,
  418.                                            aItem);
  419.  
  420.         // notify observers
  421.         this.mObservers.notify("onAddItem", [aItem]);
  422.     },
  423.  
  424.     // void modifyItem( in calIItemBase aNewItem, in calIItemBase aOldItem, in calIOperationListener aListener );
  425.     modifyItem: function (aNewItem, aOldItem, aListener) {
  426.         if (this.readOnly) 
  427.             throw Components.interfaces.calIErrors.CAL_IS_READONLY;
  428.         function reportError(errId, errStr) {
  429.             if (aListener)
  430.                 aListener.onOperationComplete (this,
  431.                                                errId ? errId : Components.results.NS_ERROR_FAILURE,
  432.                                                aListener.MODIFY,
  433.                                                aNewItem.id,
  434.                                                errStr);
  435.         }
  436.  
  437.         if (aNewItem.id == null) {
  438.             // this is definitely an error
  439.             reportError (null, "ID for modifyItem item is null");
  440.             return;
  441.         }
  442.  
  443.         // Ensure that we're looking at the base item
  444.         // if we were given an occurrence.  Later we can
  445.         // optimize this.
  446.         if (aNewItem.parentItem != aNewItem) {
  447.             aNewItem.parentItem.recurrenceInfo.modifyException(aNewItem);
  448.         }
  449.  
  450.         aNewItem = aNewItem.parentItem;
  451.         aOldItem = aOldItem.parentItem;
  452.  
  453.         // get the old item
  454.         var olditem = this.getItemById(aOldItem.id);
  455.         if (!olditem) {
  456.             // no old item found?  should be using addItem, then.
  457.             if (aListener)
  458.                 aListener.onOperationComplete (this,
  459.                                                Components.results.NS_ERROR_FAILURE,
  460.                                                aListener.MODIFY,
  461.                                                aNewItem.id,
  462.                                                "ID does not already exist for modifyItem");
  463.             return;
  464.         }
  465.  
  466.         if (aOldItem.generation != aNewItem.generation) {
  467.             if (aListener)
  468.                 aListener.onOperationComplete (this,
  469.                                                Components.results.NS_ERROR_FAILURE,
  470.                                                aListener.MODIFY,
  471.                                                aNewItem.id,
  472.                                                "generation too old for for modifyItem");
  473.             return;
  474.         }
  475.  
  476.         var modifiedItem = aNewItem.clone();
  477.         modifiedItem.generation += 1;
  478.         modifiedItem.makeImmutable();
  479.  
  480.         this.flushItem (modifiedItem, aOldItem);
  481.  
  482.         if (aListener)
  483.             aListener.onOperationComplete (this,
  484.                                            Components.results.NS_OK,
  485.                                            aListener.MODIFY,
  486.                                            modifiedItem.id,
  487.                                            modifiedItem);
  488.  
  489.         // notify observers
  490.         this.mObservers.notify("onModifyItem", [modifiedItem, aOldItem]);
  491.     },
  492.  
  493.     // void deleteItem( in string id, in calIOperationListener aListener );
  494.     deleteItem: function (aItem, aListener) {
  495.         if (this.readOnly) 
  496.             throw Components.interfaces.calIErrors.CAL_IS_READONLY;
  497.         if (aItem.parentItem != aItem) {
  498.             aItem.parentItem.recurrenceInfo.removeExceptionFor(aItem.recurrenceId);
  499.             return;
  500.         }
  501.  
  502.         if (aItem.id == null) {
  503.             if (aListener)
  504.                 aListener.onOperationComplete (this,
  505.                                                Components.results.NS_ERROR_FAILURE,
  506.                                                aListener.DELETE,
  507.                                                null,
  508.                                                "ID is null for deleteItem");
  509.             return;
  510.         }
  511.  
  512.         this.deleteItemById(aItem.id);
  513.  
  514.         if (aListener)
  515.             aListener.onOperationComplete (this,
  516.                                            Components.results.NS_OK,
  517.                                            aListener.DELETE,
  518.                                            aItem.id,
  519.                                            null);
  520.  
  521.         // notify observers 
  522.         this.mObservers.notify("onDeleteItem", [aItem]);
  523.     },
  524.  
  525.     // void getItem( in string id, in calIOperationListener aListener );
  526.     getItem: function (aId, aListener) {
  527.         if (!aListener)
  528.             return;
  529.  
  530.         var item = this.getItemById (aId);
  531.         if (!item) {
  532.             aListener.onOperationComplete (this,
  533.                                            Components.results.NS_ERROR_FAILURE,
  534.                                            aListener.GET,
  535.                                            aId,
  536.                                            "ID doesn't exist for getItem");
  537.         }
  538.  
  539.         var item_iid = null;
  540.         if (item instanceof Components.interfaces.calIEvent)
  541.             item_iid = Components.interfaces.calIEvent;
  542.         else if (item instanceof Components.interfaces.calITodo)
  543.             item_iid = Components.interfaces.calITodo;
  544.         else {
  545.             aListener.onOperationComplete (this,
  546.                                            Components.results.NS_ERROR_FAILURE,
  547.                                            aListener.GET,
  548.                                            aId,
  549.                                            "Can't deduce item type based on QI");
  550.             return;
  551.         }
  552.  
  553.         aListener.onGetResult (this,
  554.                                Components.results.NS_OK,
  555.                                item_iid, null,
  556.                                1, [item]);
  557.  
  558.         aListener.onOperationComplete (this,
  559.                                        Components.results.NS_OK,
  560.                                        aListener.GET,
  561.                                        aId,
  562.                                        null);
  563.     },
  564.  
  565.     // void getItems( in unsigned long aItemFilter, in unsigned long aCount, 
  566.     //                in calIDateTime aRangeStart, in calIDateTime aRangeEnd,
  567.     //                in calIOperationListener aListener );
  568.     getItems: function (aItemFilter, aCount,
  569.                         aRangeStart, aRangeEnd, aListener)
  570.     {
  571.         //var profStartTime = Date.now();
  572.         if (!aListener)
  573.             return;
  574.  
  575.         var self = this;
  576.  
  577.         var itemsFound = Array();
  578.         var startTime = -0x7fffffffffffffff;
  579.         // endTime needs to be the max value a PRTime can be
  580.         var endTime = 0x7fffffffffffffff;
  581.         var count = 0;
  582.         if (aRangeStart)
  583.             startTime = aRangeStart.nativeTime;
  584.         if (aRangeEnd)
  585.             endTime = aRangeEnd.nativeTime;
  586.  
  587.         var wantEvents = ((aItemFilter & kCalICalendar.ITEM_FILTER_TYPE_EVENT) != 0);
  588.         var wantTodos = ((aItemFilter & kCalICalendar.ITEM_FILTER_TYPE_TODO) != 0);
  589.         var asOccurrences = ((aItemFilter & kCalICalendar.ITEM_FILTER_CLASS_OCCURRENCES) != 0);
  590.         if (!wantEvents && !wantTodos) {
  591.             // nothing to do
  592.             aListener.onOperationComplete (this,
  593.                                            Components.results.NS_OK,
  594.                                            aListener.GET,
  595.                                            null,
  596.                                            null);
  597.             return;
  598.         }
  599.  
  600.         var wantCompletedItems = ((aItemFilter & kCalICalendar.ITEM_FILTER_COMPLETED_YES) != 0);
  601.         var wantNotCompletedItems = ((aItemFilter & kCalICalendar.ITEM_FILTER_COMPLETED_NO) != 0);
  602.         
  603.         // sending items to the listener 1 at a time sucks. instead,
  604.         // queue them up.
  605.         // if we ever have more than maxQueueSize items outstanding,
  606.         // call the listener.  Calling with null theItems forces
  607.         // a send and a queue clear.
  608.         var maxQueueSize = 10;
  609.         var queuedItems = [ ];
  610.         var queuedItemsIID;
  611.         function queueItems(theItems, theIID) {
  612.             // if we're about to start sending a different IID,
  613.             // flush the queue
  614.             if (theIID && queuedItemsIID != theIID) {
  615.                 if (queuedItemsIID)
  616.                     queueItems(null);
  617.                 queuedItemsIID = theIID;
  618.             }
  619.  
  620.             if (theItems)
  621.                 queuedItems = queuedItems.concat(theItems);
  622.  
  623.             if (queuedItems.length != 0 && (!theItems || queuedItems.length > maxQueueSize)) {
  624.                 //var listenerStart = Date.now();
  625.                 aListener.onGetResult(self,
  626.                                       Components.results.NS_OK,
  627.                                       queuedItemsIID, null,
  628.                                       queuedItems.length, queuedItems);
  629.                 //var listenerEnd = Date.now();
  630.                 //dump ("++++ listener callback took: " + (listenerEnd - listenerStart) + " ms\n");
  631.  
  632.                 queuedItems = [ ];
  633.             }
  634.         }
  635.  
  636.         // helper function to handle converting a row to an item,
  637.         // expanding occurrences, and queue the items for the listener
  638.         function handleResultItem(item, flags, theIID) {
  639.             self.getAdditionalDataForItem(item, flags);
  640.             item.makeImmutable();
  641.  
  642.             var expandedItems;
  643.  
  644.             if (asOccurrences && item.recurrenceInfo) {
  645.                 expandedItems = item.recurrenceInfo.getOccurrences (aRangeStart, aRangeEnd, 0, {});
  646.             } else {
  647.                 expandedItems = [ item ];
  648.             }
  649.  
  650.             queueItems (expandedItems, theIID);
  651.  
  652.             return expandedItems.length;
  653.         }
  654.  
  655.         // check the count and send end if count is exceeded
  656.         function checkCount() {
  657.             if (aCount && count >= aCount) {
  658.                 // flush queue
  659.                 queueItems(null);
  660.  
  661.                 // send operation complete
  662.                 aListener.onOperationComplete (self,
  663.                                                Components.results.NS_OK,
  664.                                                aListener.GET,
  665.                                                null,
  666.                                                null);
  667.  
  668.                 // tell caller we're done
  669.                 return true;
  670.             }
  671.  
  672.             return false;
  673.         }
  674.  
  675.         // First fetch all the events
  676.         if (wantEvents) {
  677.             // this will contain a lookup table of item ids that we've already dealt with,
  678.             // if we have recurrence to care about.
  679.             var handledRecurringEvents = { };
  680.             var sp;             // stmt params
  681.  
  682.             var resultItems = [];
  683.  
  684.             // first get non-recurring events and recurring events that happen
  685.             // to fall within the range
  686.             sp = this.mSelectEventsByRange.params;
  687.             sp.cal_id = this.mCalId;
  688.             sp.range_start = startTime;
  689.             sp.range_end = endTime;
  690.             sp.start_offset = aRangeStart ? aRangeStart.timezoneOffset * USECS_PER_SECOND : 0;
  691.             sp.end_offset = aRangeEnd ? aRangeEnd.timezoneOffset * USECS_PER_SECOND : 0;
  692.  
  693.             while (this.mSelectEventsByRange.step()) {
  694.                 var row = this.mSelectEventsByRange.row;
  695.                 var flags = {};
  696.                 var item = this.getEventFromRow(row, flags);
  697.                 flags = flags.value;
  698.  
  699.                 resultItems.push({item: item, flags: flags});
  700.  
  701.                 if (asOccurrences && flags & CAL_ITEM_FLAG_HAS_RECURRENCE)
  702.                     handledRecurringEvents[row.id] = true;
  703.             }
  704.             this.mSelectEventsByRange.reset();
  705.  
  706.             // then, if we want occurrences, we need to query database-wide.. yuck
  707.             if (asOccurrences) {
  708.                 sp = this.mSelectEventsWithRecurrence.params;
  709.                 sp.cal_id = this.mCalId;
  710.                 while (this.mSelectEventsWithRecurrence.step()) {
  711.                     var row = this.mSelectEventsWithRecurrence.row;
  712.                     // did we already deal with this event id?
  713.                     if (row.id in handledRecurringEvents &&
  714.                         handledRecurringEvents[row.id] == true) {
  715.                         continue;
  716.                     }
  717.  
  718.                     var flags = {};
  719.                     var item = this.getEventFromRow(row, flags);
  720.                     flags = flags.value;
  721.  
  722.                     resultItems.push({item: item, flags: flags});
  723.                 }
  724.                 this.mSelectEventsWithRecurrence.reset();
  725.             }
  726.  
  727.             // process the events
  728.             for each (var evitem in resultItems) {
  729.                 count += handleResultItem(evitem.item, evitem.flags, Components.interfaces.calIEvent);
  730.                 if (checkCount())
  731.                     return;
  732.             }
  733.         }
  734.  
  735.  
  736.         // if todos are wanted, do them next
  737.         if (wantTodos) {
  738.             // this will contain a lookup table of item ids that we've already dealt with,
  739.             // if we have recurrence to care about.
  740.             var handledRecurringTodos = { };
  741.             var sp;             // stmt params
  742.  
  743.             var resultItems = [];
  744.  
  745.             // first get non-recurring todos and recurring todos that happen
  746.             // to fall within the range
  747.             sp = this.mSelectTodosByRange.params;
  748.             sp.cal_id = this.mCalId;
  749.             sp.range_start = startTime;
  750.             sp.range_end = endTime;
  751.             sp.start_offset = aRangeStart ? aRangeStart.timezoneOffset * USECS_PER_SECOND : 0;
  752.             sp.end_offset = aRangeEnd ? aRangeEnd.timezoneOffset * USECS_PER_SECOND : 0;
  753.  
  754.             while (this.mSelectTodosByRange.step()) {
  755.                 var row = this.mSelectTodosByRange.row;
  756.                 var flags = {};
  757.                 var item = this.getTodoFromRow(row, flags);
  758.                 flags = flags.value;
  759.  
  760.                 if (!item.isCompleted && !wantNotCompletedItems)
  761.                     continue;
  762.                 if (item.isCompleted && !wantCompletedItems)
  763.                     continue;
  764.  
  765.                 var completed = 
  766.                 resultItems.push({item: item, flags: flags});
  767.                 if (asOccurrences && row.flags & CAL_ITEM_FLAG_HAS_RECURRENCE)
  768.                     handledRecurringTodos[row.id] = true;
  769.             }
  770.             this.mSelectTodosByRange.reset();
  771.  
  772.             // then, if we want occurrences, we need to query database-wide.. yuck
  773.             if (asOccurrences) {
  774.                 sp = this.mSelectTodosWithRecurrence.params;
  775.                 sp.cal_id = this.mCalId;
  776.                 while (this.mSelectTodosWithRecurrence.step()) {
  777.                     var row = this.mSelectTodosWithRecurrence.row;
  778.                     // did we already deal with this todo id?
  779.                     if (handledRecurringTodos[row.id] == true)
  780.                         continue;
  781.  
  782.                     var flags = {};
  783.                     var item = this.getTodoFromRow(row, flags);
  784.                     flags = flags.value;
  785.  
  786.                     var itemIsCompleted = false;
  787.                     if (item.todo_complete == 100 ||
  788.                         item.todo_completed != null ||
  789.                         item.ical_status == Components.interfaces.calITodo.CAL_TODO_STATUS_COMPLETED)
  790.                         itemIsCompleted = true;
  791.  
  792.                     if (!itemIsCompleted && !wantNotCompletedItems)
  793.                         continue;
  794.                     if (itemIsCompleted && !wantCompletedItems)
  795.                         continue;
  796.  
  797.                     resultItems.push({item: item, flags: flags});
  798.                 }
  799.                 this.mSelectTodosWithRecurrence.reset();
  800.             }
  801.  
  802.             // process the todos
  803.             for each (var todoitem in resultItems) {
  804.                 count += handleResultItem(todoitem.item, todoitem.flags, Components.interfaces.calITodo);
  805.                 if (checkCount())
  806.                     return;
  807.             }
  808.         }
  809.  
  810.         // flush the queue
  811.         queueItems(null);
  812.  
  813.         // and finish
  814.         aListener.onOperationComplete (this,
  815.                                        Components.results.NS_OK,
  816.                                        aListener.GET,
  817.                                        null,
  818.                                        null);
  819.  
  820.         //var profEndTime = Date.now();
  821.         //dump ("++++ getItems took: " + (profEndTime - profStartTime) + " ms\n");
  822.     },
  823.  
  824.     startBatch: function ()
  825.     {
  826.         this.mObservers.notify("onStartBatch");
  827.     },
  828.     endBatch: function ()
  829.     {
  830.         this.mObservers.notify("onEndBatch");
  831.     },
  832.  
  833.     //
  834.     // Helper functions
  835.     //
  836.  
  837.     //
  838.     // database handling
  839.     //
  840.  
  841.     // initialize the database schema.
  842.     // needs to do some version checking
  843.     initDBSchema: function () {
  844.         for (table in sqlTables) {
  845.             dump (table + "\n");
  846.             try {
  847.                 this.mDB.executeSimpleSQL("DROP TABLE " + table);
  848.             } catch (e) { }
  849.             this.mDB.createTable(table, sqlTables[table]);
  850.         }
  851.  
  852.         // Add a version stamp to the schema
  853.         this.mDB.executeSimpleSQL("INSERT INTO cal_calendar_schema_version VALUES(" + this.DB_SCHEMA_VERSION + ")");
  854.     },
  855.  
  856.     DB_SCHEMA_VERSION: 7,
  857.  
  858.     /** 
  859.      * @return      db schema version
  860.      * @exception   various, depending on error
  861.      */
  862.     getVersion: function calStorageGetVersion() {
  863.         var selectSchemaVersion;
  864.         var version = null;
  865.  
  866.         try {
  867.             selectSchemaVersion = createStatement(this.mDB, 
  868.                                   "SELECT version FROM " +
  869.                                   "cal_calendar_schema_version LIMIT 1");
  870.             if (selectSchemaVersion.step()) {
  871.                 version = selectSchemaVersion.row.version;
  872.             }
  873.             selectSchemaVersion.reset();
  874.  
  875.             if (version !== null) {
  876.                 // This is the only place to leave this function gracefully.
  877.                 return version;
  878.             }
  879.         } catch (e) {
  880.             if (selectSchemaVersion) {
  881.                 selectSchemaVersion.reset();
  882.             }
  883.             dump ("++++++++++++ calStorageGetVersion() error: " +
  884.                   this.mDB.lastErrorString + "\n");
  885.             Components.utils.reportError("Error getting storage calendar " +
  886.                                          "schema version! DB Error: " + 
  887.                                          this.mDB.lastErrorString);
  888.             throw e;
  889.         }
  890.  
  891.         throw "cal_calendar_schema_version SELECT returned no results";
  892.     },
  893.  
  894.     upgradeDB: function (oldVersion) {
  895.         // some common helpers
  896.         function addColumn(db, tableName, colName, colType) {
  897.             db.executeSimpleSQL("ALTER TABLE " + tableName + " ADD COLUMN " + colName + " " + colType);
  898.         }
  899.  
  900.         if (oldVersion == 2 && this.DB_SCHEMA_VERSION >= 3) {
  901.             dump ("**** Upgrading schema from 2 -> 3\n");
  902.  
  903.             this.mDB.beginTransaction();
  904.             try {
  905.                 // the change between 2 and 3 includes the splitting of cal_items into
  906.                 // cal_events and cal_todos, and the addition of columns for
  907.                 // event_start_tz, event_end_tz, todo_entry_tz, todo_due_tz.
  908.                 // These need to default to "UTC" if their corresponding time is
  909.                 // given, since that's what the default was for v2 calendars
  910.  
  911.                 // create the two new tables
  912.                 try { this.mDB.executeSimpleSQL("DROP TABLE cal_events; DROP TABLE cal_todos;"); } catch (e) { }
  913.                 this.mDB.createTable("cal_events", sqlTables["cal_events"]);
  914.                 this.mDB.createTable("cal_todos", sqlTables["cal_todos"]);
  915.  
  916.                 // copy stuff over
  917.                 var eventCols = ["cal_id", "id", "time_created", "last_modified", "title",
  918.                                  "priority", "privacy", "ical_status", "flags",
  919.                                  "event_start", "event_end", "event_stamp"];
  920.                 var todoCols = ["cal_id", "id", "time_created", "last_modified", "title",
  921.                                 "priority", "privacy", "ical_status", "flags",
  922.                                 "todo_entry", "todo_due", "todo_completed", "todo_complete"];
  923.  
  924.                 this.mDB.executeSimpleSQL("INSERT INTO cal_events(" + eventCols.join(",") + ") " +
  925.                                           "     SELECT " + eventCols.join(",") +
  926.                                           "       FROM cal_items WHERE item_type = 0");
  927.                 this.mDB.executeSimpleSQL("INSERT INTO cal_todos(" + todoCols.join(",") + ") " +
  928.                                           "     SELECT " + todoCols.join(",") +
  929.                                           "       FROM cal_items WHERE item_type = 1");
  930.  
  931.                 // now fix up the new _tz columns
  932.                 this.mDB.executeSimpleSQL("UPDATE cal_events SET event_start_tz = 'UTC' WHERE event_start IS NOT NULL");
  933.                 this.mDB.executeSimpleSQL("UPDATE cal_events SET event_end_tz = 'UTC' WHERE event_end IS NOT NULL");
  934.                 this.mDB.executeSimpleSQL("UPDATE cal_todos SET todo_entry_tz = 'UTC' WHERE todo_entry IS NOT NULL");
  935.                 this.mDB.executeSimpleSQL("UPDATE cal_todos SET todo_due_tz = 'UTC' WHERE todo_due IS NOT NULL");
  936.                 this.mDB.executeSimpleSQL("UPDATE cal_todos SET todo_completed_tz = 'UTC' WHERE todo_completed IS NOT NULL");
  937.  
  938.                 // finally update the version
  939.                 this.mDB.executeSimpleSQL("DELETE FROM cal_calendar_schema_version; INSERT INTO cal_calendar_schema_version VALUES (3);");
  940.  
  941.                 this.mDB.commitTransaction();
  942.  
  943.                 oldVersion = 3;
  944.             } catch (e) {
  945.                 dump ("+++++++++++++++++ DB Error: " + this.mDB.lastErrorString + "\n");
  946.                 Components.utils.reportError("Upgrade failed! DB Error: " + 
  947.                                              this.mDB.lastErrorString);
  948.                 this.mDB.rollbackTransaction();
  949.                 throw e;
  950.             }
  951.         }
  952.  
  953.         if (oldVersion == 3 && this.DB_SCHEMA_VERSION >= 4) {
  954.             dump ("**** Upgrading schema from 3 -> 4\n");
  955.  
  956.             this.mDB.beginTransaction();
  957.             try {
  958.                 // the change between 3 and 4 is the addition of
  959.                 // recurrence_id and recurrence_id_tz columns to
  960.                 // cal_events, cal_todos, cal_attendees, and cal_properties
  961.                 addColumn(this.mDB, "cal_events", "recurrence_id", "INTEGER");
  962.                 addColumn(this.mDB, "cal_events", "recurrence_id_tz", "VARCHAR");
  963.  
  964.                 addColumn(this.mDB, "cal_todos", "recurrence_id", "INTEGER");
  965.                 addColumn(this.mDB, "cal_todos", "recurrence_id_tz", "VARCHAR");
  966.  
  967.                 addColumn(this.mDB, "cal_attendees", "recurrence_id", "INTEGER");
  968.                 addColumn(this.mDB, "cal_attendees", "recurrence_id_tz", "VARCHAR");
  969.  
  970.                 addColumn(this.mDB, "cal_properties", "recurrence_id", "INTEGER");
  971.                 addColumn(this.mDB, "cal_properties", "recurrence_id_tz", "VARCHAR");
  972.  
  973.                 this.mDB.executeSimpleSQL("DELETE FROM cal_calendar_schema_version; INSERT INTO cal_calendar_schema_version VALUES (4);");
  974.                 this.mDB.commitTransaction();
  975.  
  976.                 oldVersion = 4;
  977.             } catch (e) {
  978.                 dump ("+++++++++++++++++ DB Error: " + this.mDB.lastErrorString + "\n");
  979.                 Components.utils.reportError("Upgrade failed! DB Error: " +
  980.                                              this.mDB.lastErrorString);
  981.                 this.mDB.rollbackTransaction();
  982.                 throw e;
  983.             }
  984.         }
  985.  
  986.         if (oldVersion == 4 && this.DB_SCHEMA_VERSION >= 5) {
  987.             dump ("**** Upgrading schema from 4 -> 5\n");
  988.  
  989.             this.mDB.beginTransaction();
  990.             try {
  991.                 // the change between 4 and 5 is the addition of alarm_offset
  992.                 // and alarm_last_ack columns.  The alarm_time column is not
  993.                 // used in this version, but will likely return in future versions
  994.                 // so it is not being removed
  995.                 addColumn(this.mDB, "cal_events", "alarm_offset", "INTEGER");
  996.                 addColumn(this.mDB, "cal_events", "alarm_related", "INTEGER");
  997.                 addColumn(this.mDB, "cal_events", "alarm_last_ack", "INTEGER");
  998.  
  999.                 addColumn(this.mDB, "cal_todos", "alarm_offset", "INTEGER");
  1000.                 addColumn(this.mDB, "cal_todos", "alarm_related", "INTEGER");
  1001.                 addColumn(this.mDB, "cal_todos", "alarm_last_ack", "INTEGER");
  1002.  
  1003.                 this.mDB.executeSimpleSQL("UPDATE cal_calendar_schema_version SET version = 5;");
  1004.                 this.mDB.commitTransaction();
  1005.                 oldVersion = 5;
  1006.             } catch (e) {
  1007.                 dump ("+++++++++++++++++ DB Error: " + this.mDB.lastErrorString + "\n");
  1008.                 Components.utils.reportError("Upgrade failed! DB Error: " +
  1009.                                              this.mDB.lastErrorString);
  1010.                 this.mDB.rollbackTransaction();
  1011.                 throw e;
  1012.             }
  1013.         }
  1014.  
  1015.         if (oldVersion == 5 && this.DB_SCHEMA_VERSION >= 6) {
  1016.             dump ("**** Upgrading schema from 5 -> 6\n");
  1017.  
  1018.             this.mDB.beginTransaction();
  1019.             try {
  1020.                 // Schema changes between v5 and v6:
  1021.                 //
  1022.                 // - Change all STRING columns to TEXT to avoid SQLite's
  1023.                 //   "feature" where it will automatically convert strings to
  1024.                 //   numbers (ex: 10e4 -> 10000). See bug 333688.
  1025.  
  1026.  
  1027.                 // Create the new tables.
  1028.                 var tableNames = ["cal_events", "cal_todos", "cal_attendees",
  1029.                                   "cal_recurrence", "cal_properties"];
  1030.  
  1031.                 var query = "";
  1032.                 try { 
  1033.                     for (var i in tableNames) {
  1034.                         query += "DROP TABLE " + tableNames[i] + "_v6;"
  1035.                     }
  1036.                     this.mDB.executeSimpleSQL(query);
  1037.                 } catch (e) {
  1038.                     // We should get exceptions for trying to drop tables
  1039.                     // that don't (shouldn't) exist.
  1040.                 }
  1041.  
  1042.                 this.mDB.createTable("cal_events_v6", sqlTables["cal_events"]);
  1043.                 this.mDB.createTable("cal_todos_v6", sqlTables["cal_todos"]);
  1044.                 this.mDB.createTable("cal_attendees_v6", sqlTables["cal_attendees"]);
  1045.                 this.mDB.createTable("cal_recurrence_v6", sqlTables["cal_recurrence"]);
  1046.                 this.mDB.createTable("cal_properties_v6", sqlTables["cal_properties"]);
  1047.  
  1048.  
  1049.                 // Copy in the data.
  1050.                 var cal_events_cols = ["cal_id", "id", "time_created",
  1051.                                        "last_modified", "title", "priority",
  1052.                                        "privacy", "ical_status",
  1053.                                        "recurrence_id", "recurrence_id_tz",
  1054.                                        "flags", "event_start",
  1055.                                        "event_start_tz", "event_end",
  1056.                                        "event_end_tz", "event_stamp",
  1057.                                        "alarm_time", "alarm_time_tz",
  1058.                                        "alarm_offset", "alarm_related",
  1059.                                        "alarm_last_ack"];
  1060.  
  1061.                 var cal_todos_cols = ["cal_id", "id", "time_created",
  1062.                                       "last_modified", "title", "priority",
  1063.                                       "privacy", "ical_status",
  1064.                                       "recurrence_id", "recurrence_id_tz",
  1065.                                       "flags", "todo_entry", "todo_entry_tz",
  1066.                                       "todo_due", "todo_due_tz",
  1067.                                       "todo_completed", "todo_completed_tz",
  1068.                                       "todo_complete", "alarm_time",
  1069.                                       "alarm_time_tz", "alarm_offset",
  1070.                                       "alarm_related", "alarm_last_ack"];
  1071.  
  1072.                 var cal_attendees_cols = ["item_id", "recurrence_id",
  1073.                                           "recurrence_id_tz", "attendee_id",
  1074.                                           "common_name", "rsvp", "role",
  1075.                                           "status", "type"];
  1076.  
  1077.                 var cal_recurrence_cols = ["item_id", "recur_index",
  1078.                                            "recur_type", "is_negative",
  1079.                                            "dates", "count", "end_date",
  1080.                                            "interval", "second", "minute",
  1081.                                            "hour", "day", "monthday",
  1082.                                            "yearday", "weekno", "month",
  1083.                                            "setpos"];
  1084.  
  1085.                 var cal_properties_cols = ["item_id", "recurrence_id",
  1086.                                            "recurrence_id_tz", "key",
  1087.                                            "value"];
  1088.  
  1089.                 var theDB = this.mDB;
  1090.                 function copyDataOver(aTableName, aColumnNames) {
  1091.                     theDB.executeSimpleSQL("INSERT INTO " + aTableName + "_v6(" + aColumnNames.join(",") + ") " + 
  1092.                                            "     SELECT " + aColumnNames.join(",") + 
  1093.                                            "       FROM " + aTableName + ";");
  1094.                 }
  1095.  
  1096.                 copyDataOver("cal_events", cal_events_cols);
  1097.                 copyDataOver("cal_todos", cal_todos_cols);
  1098.                 copyDataOver("cal_attendees", cal_attendees_cols);
  1099.                 copyDataOver("cal_recurrence", cal_recurrence_cols);
  1100.                 copyDataOver("cal_properties", cal_properties_cols);
  1101.  
  1102.  
  1103.                 // Delete each old table and rename the new ones to use the
  1104.                 // old tables' names.
  1105.                 for (var i in tableNames) {
  1106.                     this.mDB.executeSimpleSQL("DROP TABLE  " + tableNames[i] + ";" +
  1107.                                               "ALTER TABLE " + tableNames[i] + "_v6" + 
  1108.                                               "  RENAME TO " + tableNames[i] + ";");
  1109.                 }
  1110.  
  1111.  
  1112.                 // Update the version stamp, and commit.
  1113.                 this.mDB.executeSimpleSQL("UPDATE cal_calendar_schema_version SET version = 6;");
  1114.                 this.mDB.commitTransaction();
  1115.                 oldVersion = 6;
  1116.             } catch (e) {
  1117.                 dump ("+++++++++++++++++ DB Error: " + this.mDB.lastErrorString + "\n");
  1118.                 Components.utils.reportError("Upgrade failed! DB Error: " +
  1119.                                              this.mDB.lastErrorString);
  1120.                 this.mDB.rollbackTransaction();
  1121.                 throw e;
  1122.             }
  1123.         }
  1124.  
  1125.         if (oldVersion == 6 && this.DB_SCHEMA_VERSION >= 7) {
  1126.             dump ("**** Upgrading schema from 6 -> 7\n");
  1127.  
  1128.             var getTzIds;
  1129.             this.mDB.beginTransaction();
  1130.             try {
  1131.                 // Schema changes between v6 and v7:
  1132.                 //
  1133.                 // - Migrate all stored mozilla.org timezones from 20050126_1
  1134.                 //   to 20070129_1.  Note that there are some exceptions where
  1135.                 //   timezones were deleted and/or renamed.
  1136.  
  1137.                 // Get a list of the /mozilla.org/* timezones used in the db
  1138.                 var tzId;
  1139.                 getTzIds = createStatement(this.mDB,
  1140.                     "SELECT DISTINCT(zone) FROM ("+
  1141.                         "SELECT recurrence_id_tz AS zone FROM cal_attendees  WHERE recurrence_id_tz LIKE '/mozilla.org%' UNION " +
  1142.                         "SELECT recurrence_id_tz AS zone FROM cal_events     WHERE recurrence_id_tz LIKE '/mozilla.org%' UNION " +
  1143.                         "SELECT event_start_tz   AS zone FROM cal_events     WHERE event_start_tz   LIKE '/mozilla.org%' UNION " +
  1144.                         "SELECT event_end_tz     AS zone FROM cal_events     WHERE event_end_tz     LIKE '/mozilla.org%' UNION " +
  1145.                         "SELECT alarm_time_tz    AS zone FROM cal_events     WHERE alarm_time_tz    LIKE '/mozilla.org%' UNION " +
  1146.                         "SELECT recurrence_id_tz AS zone FROM cal_properties WHERE recurrence_id_tz LIKE '/mozilla.org%' UNION " +
  1147.                         "SELECT recurrence_id_tz AS zone FROM cal_todos      WHERE recurrence_id_tz LIKE '/mozilla.org%' UNION " +
  1148.                         "SELECT todo_entry_tz    AS zone FROM cal_todos      WHERE todo_entry_tz    LIKE '/mozilla.org%' UNION " +
  1149.                         "SELECT todo_due_tz      AS zone FROM cal_todos      WHERE todo_due_tz      LIKE '/mozilla.org%' UNION " +
  1150.                         "SELECT alarm_time_tz    AS zone FROM cal_todos      WHERE alarm_time_tz    LIKE '/mozilla.org%'" +
  1151.                     ");");
  1152.  
  1153.                 var tzIdsToUpdate = [];
  1154.                 var updateTzIds = false; // Perform the SQL UPDATE, or not.
  1155.                 while (getTzIds.step()) {
  1156.                     tzId = getTzIds.row.zone;
  1157.  
  1158.                     // Send the timezones off to the ICS service to attempt
  1159.                     // conversion.
  1160.                     icsSvc = Components.classes["@mozilla.org/calendar/ics-service;1"]
  1161.                                        .getService(Components.interfaces.calIICSService);
  1162.                     var latestTzId = icsSvc.latestTzId(tzId);
  1163.                     if ((latestTzId) && (tzId != latestTzId)) {
  1164.                         tzIdsToUpdate.push({oldTzId: tzId, newTzId: latestTzId});
  1165.                         updateTzIds = true;
  1166.                     }
  1167.                 }
  1168.                 getTzIds.reset();
  1169.  
  1170.                 if (updateTzIds) {
  1171.                     // We've got stuff to update!
  1172.                     for each (var update in tzIdsToUpdate) {
  1173.                         this.mDB.executeSimpleSQL(
  1174.                             "UPDATE cal_attendees  SET recurrence_id_tz = '" + update.newTzId + "' WHERE recurrence_id_tz = '" + update.oldTzId + "'; " +
  1175.                             "UPDATE cal_events     SET recurrence_id_tz = '" + update.newTzId + "' WHERE recurrence_id_tz = '" + update.oldTzId + "'; " +
  1176.                             "UPDATE cal_events     SET event_start_tz   = '" + update.newTzId + "' WHERE event_start_tz   = '" + update.oldTzId + "'; " +
  1177.                             "UPDATE cal_events     SET event_end_tz     = '" + update.newTzId + "' WHERE event_end_tz     = '" + update.oldTzId + "'; " +
  1178.                             "UPDATE cal_events     SET alarm_time_tz    = '" + update.newTzId + "' WHERE alarm_time_tz    = '" + update.oldTzId + "'; " +
  1179.                             "UPDATE cal_properties SET recurrence_id_tz = '" + update.newTzId + "' WHERE recurrence_id_tz = '" + update.oldTzId + "'; " +
  1180.                             "UPDATE cal_todos      SET recurrence_id_tz = '" + update.newTzId + "' WHERE recurrence_id_tz = '" + update.oldTzId + "'; " +
  1181.                             "UPDATE cal_todos      SET todo_entry_tz    = '" + update.newTzId + "' WHERE todo_entry_tz    = '" + update.oldTzId + "'; " +
  1182.                             "UPDATE cal_todos      SET todo_due_tz      = '" + update.newTzId + "' WHERE todo_due_tz      = '" + update.oldTzId + "'; " +
  1183.                             "UPDATE cal_todos      SET alarm_time_tz    = '" + update.newTzId + "' WHERE recurrence_id_tz = '" + update.oldTzId + "';");
  1184.                     }
  1185.                 }
  1186.  
  1187.                 // Update the version stamp, and commit.
  1188.                 this.mDB.executeSimpleSQL("UPDATE cal_calendar_schema_version SET version = 7;");
  1189.                 this.mDB.commitTransaction();
  1190.                 oldVersion = 7;
  1191.             } catch (e) {
  1192.                 dump ("+++++++++++++++++ DB Error: " + this.mDB.lastErrorString + "\n");
  1193.                 Components.utils.reportError("Upgrade failed! DB Error: " +
  1194.                                              this.mDB.lastErrorString);
  1195.                 this.mDB.rollbackTransaction();
  1196.                 throw e;
  1197.             }
  1198.         }
  1199.  
  1200.         if (oldVersion != 7) {
  1201.             dump ("#######!!!!! calStorageCalendar Schema Update failed -- db version: " + oldVersion + " this version: " + this.DB_SCHEMA_VERSION + "\n");
  1202.             throw Components.results.NS_ERROR_FAILURE;
  1203.         }
  1204.     },
  1205.  
  1206.     // database initialization
  1207.     // assumes mDB is valid
  1208.  
  1209.     initDB: function () {
  1210.         if (!this.mDB.tableExists("cal_calendar_schema_version")) {
  1211.             dump("*** cal_calendar_schema_version not found; " +
  1212.          "initializing storage provider tables\n");
  1213.             this.initDBSchema();
  1214.         } else {
  1215.             var version = this.getVersion();
  1216.             dump ("*** Calendar schema version is: " + version + "\n");
  1217.  
  1218.             if (version != this.DB_SCHEMA_VERSION) {
  1219.                 this.upgradeDB(version);
  1220.             }
  1221.         }
  1222.         
  1223.         // (Conditionally) add index
  1224.         this.mDB.executeSimpleSQL(
  1225.             "CREATE INDEX IF NOT EXISTS " + 
  1226.             "idx_cal_properies_item_id ON cal_properties(item_id);"
  1227.             );
  1228.  
  1229.         this.mSelectEvent = createStatement (
  1230.             this.mDB,
  1231.             "SELECT * FROM cal_events " +
  1232.             "WHERE id = :id AND recurrence_id IS NULL " +
  1233.             "LIMIT 1"
  1234.             );
  1235.  
  1236.         this.mSelectTodo = createStatement (
  1237.             this.mDB,
  1238.             "SELECT * FROM cal_todos " +
  1239.             "WHERE id = :id AND recurrence_id IS NULL " +
  1240.             "LIMIT 1"
  1241.             );
  1242.  
  1243.         // The more readable version of the next where-clause is:
  1244.         //   WHERE event_end >= :range_start AND event_start < :range_end
  1245.         // but that doesn't work with floating start or end times. The logic
  1246.         // is the same though.
  1247.         // For readability, a few helpers:
  1248.         var floatingEventStart = "event_start_tz = 'floating' AND event_start"
  1249.         var nonFloatingEventStart = "event_start_tz != 'floating' AND event_start"
  1250.         var floatingEventEnd = "event_end_tz = 'floating' AND event_end"
  1251.         var nonFloatingEventEnd = "event_end_tz != 'floating' AND event_end"
  1252.         // The query needs to take both floating and non floating into account
  1253.         this.mSelectEventsByRange = createStatement(
  1254.             this.mDB,
  1255.             "SELECT * FROM cal_events " +
  1256.             "WHERE " +
  1257.             " (("+floatingEventEnd+" >= :range_start + :start_offset) OR " +
  1258.             "  ("+nonFloatingEventEnd+" >= :range_start)) AND " +
  1259.             " (("+floatingEventStart+" < :range_end + :end_offset) OR " +
  1260.             "  ("+nonFloatingEventStart+" < :range_end)) " +
  1261.             "  AND cal_id = :cal_id AND recurrence_id IS NULL"
  1262.             );
  1263.  
  1264.         var floatingTodoEntry = "todo_entry_tz = 'floating' AND todo_entry";
  1265.         var nonFloatingTodoEntry = "todo_entry_tz != 'floating' AND todo_entry";
  1266.         var floatingTodoDue = "todo_due_tz = 'floating' AND todo_due";
  1267.         var nonFloatingTodoDue = "todo_due_tz != 'floating' AND todo_due";
  1268.  
  1269.         this.mSelectTodosByRange = createStatement(
  1270.             this.mDB,
  1271.             "SELECT * FROM cal_todos " +
  1272.             "WHERE " +
  1273.             " ((("+floatingTodoDue+" >= :range_start + :start_offset) OR " +
  1274.             "   ("+nonFloatingTodoDue+" >= :range_start)) OR " +
  1275.             "  (todo_due IS NULL)) AND " +
  1276.             " ((("+floatingTodoEntry+" < :range_end + :end_offset) OR " +
  1277.             "   ("+nonFloatingTodoEntry+" < :range_end)) OR " +
  1278.             "  (todo_entry IS NULL)) " +
  1279.             " AND cal_id = :cal_id AND recurrence_id IS NULL"
  1280.             );
  1281.  
  1282.         this.mSelectEventsWithRecurrence = createStatement(
  1283.             this.mDB,
  1284.             "SELECT * FROM cal_events " +
  1285.             " WHERE flags & 16 == 16 " +
  1286.             "   AND cal_id = :cal_id AND recurrence_id is NULL"
  1287.             );
  1288.  
  1289.         this.mSelectTodosWithRecurrence = createStatement(
  1290.             this.mDB,
  1291.             "SELECT * FROM cal_todos " +
  1292.             " WHERE flags & 16 == 16 " +
  1293.             "   AND cal_id = :cal_id AND recurrence_id IS NULL"
  1294.             );
  1295.  
  1296.         this.mSelectEventExceptions = createStatement (
  1297.             this.mDB,
  1298.             "SELECT * FROM cal_events " +
  1299.             "WHERE id = :id AND recurrence_id IS NOT NULL"
  1300.             );
  1301.  
  1302.         this.mSelectTodoExceptions = createStatement (
  1303.             this.mDB,
  1304.             "SELECT * FROM cal_todos " +
  1305.             "WHERE id = :id AND recurrence_id IS NOT NULL"
  1306.             );
  1307.  
  1308.         // For the extra-item data, note that we use mDBTwo, so that
  1309.         // these can be executed while a selectItems is running!
  1310.         this.mSelectAttendeesForItem = createStatement(
  1311.             this.mDBTwo,
  1312.             "SELECT * FROM cal_attendees " +
  1313.             "WHERE item_id = :item_id AND recurrence_id IS NULL"
  1314.             );
  1315.  
  1316.         this.mSelectAttendeesForItemWithRecurrenceId = createStatement(
  1317.             this.mDBTwo,
  1318.             "SELECT * FROM cal_attendees " +
  1319.             "WHERE item_id = :item_id AND recurrence_id = :recurrence_id AND recurrence_id_tz = :recurrence_id_tz"
  1320.             );
  1321.  
  1322.         this.mSelectPropertiesForItem = createStatement(
  1323.             this.mDBTwo,
  1324.             "SELECT * FROM cal_properties " +
  1325.             "WHERE item_id = :item_id AND recurrence_id IS NULL"
  1326.             );
  1327.  
  1328.         this.mSelectPropertiesForItemWithRecurrenceId = createStatement(
  1329.             this.mDBTwo,
  1330.             "SELECT * FROM cal_properties " +
  1331.             "WHERE item_id = :item_id AND recurrence_id = :recurrence_id AND recurrence_id_tz = :recurrence_id_tz"
  1332.             );
  1333.  
  1334.         this.mSelectRecurrenceForItem = createStatement(
  1335.             this.mDBTwo,
  1336.             "SELECT * FROM cal_recurrence " +
  1337.             "WHERE item_id = :item_id " +
  1338.             "ORDER BY recur_index"
  1339.             );
  1340.  
  1341.         // insert statements
  1342.         this.mInsertEvent = createStatement (
  1343.             this.mDB,
  1344.             "INSERT INTO cal_events " +
  1345.             "  (cal_id, id, time_created, last_modified, " +
  1346.             "   title, priority, privacy, ical_status, flags, " +
  1347.             "   event_start, event_start_tz, event_end, event_end_tz, event_stamp, " +
  1348.             "   alarm_time, alarm_time_tz, recurrence_id, recurrence_id_tz, " +
  1349.             "   alarm_offset, alarm_related, alarm_last_ack) " +
  1350.             "VALUES (:cal_id, :id, :time_created, :last_modified, " +
  1351.             "        :title, :priority, :privacy, :ical_status, :flags, " +
  1352.             "        :event_start, :event_start_tz, :event_end, :event_end_tz, :event_stamp, " +
  1353.             "        :alarm_time, :alarm_time_tz, :recurrence_id, :recurrence_id_tz," + 
  1354.             "        :alarm_offset, :alarm_related, :alarm_last_ack)"
  1355.             );
  1356.  
  1357.         this.mInsertTodo = createStatement (
  1358.             this.mDB,
  1359.             "INSERT INTO cal_todos " +
  1360.             "  (cal_id, id, time_created, last_modified, " +
  1361.             "   title, priority, privacy, ical_status, flags, " +
  1362.             "   todo_entry, todo_entry_tz, todo_due, todo_due_tz, todo_completed, " +
  1363.             "   todo_completed_tz, todo_complete, " +
  1364.             "   alarm_time, alarm_time_tz, recurrence_id, recurrence_id_tz, " +
  1365.             "   alarm_offset, alarm_related, alarm_last_ack)" +
  1366.             "VALUES (:cal_id, :id, :time_created, :last_modified, " +
  1367.             "        :title, :priority, :privacy, :ical_status, :flags, " +
  1368.             "        :todo_entry, :todo_entry_tz, :todo_due, :todo_due_tz, " +
  1369.             "        :todo_completed, :todo_completed_tz, :todo_complete, " +
  1370.             "        :alarm_time, :alarm_time_tz, :recurrence_id, :recurrence_id_tz," + 
  1371.             "        :alarm_offset, :alarm_related, :alarm_last_ack)"
  1372.             );
  1373.         this.mInsertProperty = createStatement (
  1374.             this.mDB,
  1375.             "INSERT INTO cal_properties (item_id, recurrence_id, recurrence_id_tz, key, value) " +
  1376.             "VALUES (:item_id, :recurrence_id, :recurrence_id_tz, :key, :value)"
  1377.             );
  1378.         this.mInsertAttendee = createStatement (
  1379.             this.mDB,
  1380.             "INSERT INTO cal_attendees " +
  1381.             "  (item_id, recurrence_id, recurrence_id_tz, attendee_id, common_name, rsvp, role, status, type) " +
  1382.             "VALUES (:item_id, :recurrence_id, :recurrence_id_tz, :attendee_id, :common_name, :rsvp, :role, :status, :type)"
  1383.             );
  1384.         this.mInsertRecurrence = createStatement (
  1385.             this.mDB,
  1386.             "INSERT INTO cal_recurrence " +
  1387.             "  (item_id, recur_index, recur_type, is_negative, dates, count, end_date, interval, second, minute, hour, day, monthday, yearday, weekno, month, setpos) " +
  1388.             "VALUES (:item_id, :recur_index, :recur_type, :is_negative, :dates, :count, :end_date, :interval, :second, :minute, :hour, :day, :monthday, :yearday, :weekno, :month, :setpos)"
  1389.             );
  1390.  
  1391.         // delete statements
  1392.         this.mDeleteEvent = createStatement (
  1393.             this.mDB,
  1394.             "DELETE FROM cal_events WHERE id = :id"
  1395.             );
  1396.         this.mDeleteTodo = createStatement (
  1397.             this.mDB,
  1398.             "DELETE FROM cal_todos WHERE id = :id"
  1399.             );
  1400.         this.mDeleteAttendees = createStatement (
  1401.             this.mDB,
  1402.             "DELETE FROM cal_attendees WHERE item_id = :item_id"
  1403.             );
  1404.         this.mDeleteProperties = createStatement (
  1405.             this.mDB,
  1406.             "DELETE FROM cal_properties WHERE item_id = :item_id"
  1407.             );
  1408.         this.mDeleteRecurrence = createStatement (
  1409.             this.mDB,
  1410.             "DELETE FROM cal_recurrence WHERE item_id = :item_id"
  1411.             );
  1412.  
  1413.         // These are only used when deleting an entire calendar
  1414.         var extrasTables = [ "cal_attendees", "cal_properties", "cal_recurrence" ];
  1415.  
  1416.         this.mDeleteEventExtras = new Array();
  1417.         this.mDeleteTodoExtras = new Array();
  1418.  
  1419.         for (var table in extrasTables) {
  1420.             this.mDeleteEventExtras[table] = createStatement (
  1421.                 this.mDB,
  1422.                 "DELETE FROM " + extrasTables[table] + " WHERE item_id IN" +
  1423.                 "  (SELECT id FROM cal_events WHERE cal_id = :cal_id)"
  1424.                 );
  1425.             this.mDeleteTodoExtras[table] = createStatement (
  1426.                 this.mDB,
  1427.                 "DELETE FROM " + extrasTables[table] + " WHERE item_id IN" +
  1428.                 "  (SELECT id FROM cal_todos WHERE cal_id = :cal_id)"
  1429.                 );
  1430.         }
  1431.  
  1432.         // Note that you must delete the "extras" _first_ using the above two
  1433.         // statements, before you delete the events themselves.
  1434.         this.mDeleteAllEvents = createStatement (
  1435.             this.mDB,
  1436.             "DELETE from cal_events WHERE cal_id = :cal_id"
  1437.             );
  1438.         this.mDeleteAllTodos = createStatement (
  1439.             this.mDB,
  1440.             "DELETE from cal_todos WHERE cal_id = :cal_id"
  1441.             );
  1442.  
  1443.     },
  1444.  
  1445.  
  1446.     //
  1447.     // database reading functions
  1448.     //
  1449.  
  1450.     // read in the common ItemBase attributes from aDBRow, and stick
  1451.     // them on item
  1452.     getItemBaseFromRow: function (row, flags, item) {
  1453.         item.calendar = this;
  1454.         item.id = row.id;
  1455.         if (row.title)
  1456.             item.title = row.title;
  1457.         if (row.priority)
  1458.             item.priority = row.priority;
  1459.         if (row.privacy)
  1460.             item.privacy = row.privacy;
  1461.         if (row.ical_status)
  1462.             item.status = row.ical_status;
  1463.  
  1464.         if (row.alarm_time) {
  1465.             // Old (schema version 4) data, need to convert this nicely to the
  1466.             // new alarm interface.  Eventually, we're going to want to be able
  1467.             // to deal with both types of data in a calIAlarm interface, but
  1468.             // not yet.  Leaving this column around though may help ease that
  1469.             // transition in the future.
  1470.             var alarmTime = newDateTime(row.alarm_time, row.alarm_time_tz);
  1471.             var time;
  1472.             var related = Components.interfaces.calIItemBase.ALARM_RELATED_START;
  1473.             if (item instanceof Components.interfaces.calIEvent) {
  1474.                 time = newDateTime(row.event_start, row.event_start_tz);
  1475.             } else { //tasks
  1476.                 if (row.todo_entry) {
  1477.                     time = newDateTime(row.todo_entry, row.todo_entry_tz);
  1478.                 } else if (row.todo_due) {
  1479.                     related = Components.interfaces.calIItemBase.ALARM_RELATED_END;
  1480.                     time = newDateTime(row.todo_due, row.todo_due_tz);
  1481.                 }
  1482.             }
  1483.             if (time) {
  1484.                 var duration = alarmTime.subtractDate(time);
  1485.                 item.alarmOffset = duration;
  1486.                 item.alarmRelated = related;
  1487.             } else {
  1488.                 Components.utils.reportError("WARNING! Couldn't do alarm conversion for item:"+
  1489.                                              item.title+','+item.id+"!\n");
  1490.             }
  1491.         }
  1492.  
  1493.         // Alarm offset could be 0, but this is ok, so compare with null
  1494.         if (row.alarm_offset != null) {
  1495.             var duration = Components.classes["@mozilla.org/calendar/duration;1"]
  1496.                                      .createInstance(Components.interfaces.calIDuration);
  1497.             duration.inSeconds = row.alarm_offset;
  1498.             duration.normalize();
  1499.  
  1500.             item.alarmOffset = duration;
  1501.             item.alarmRelated = row.alarm_related;
  1502.             if (row.alarm_last_ack) {
  1503.                 // alarm acks are always in utc
  1504.                 item.alarmLastAck = newDateTime(row.alarm_last_ack, "UTC");
  1505.             }
  1506.         }
  1507.  
  1508.         if (row.recurrence_id)
  1509.             item.recurrenceId = newDateTime(row.recurrence_id, row.recurrence_id_tz);
  1510.  
  1511.         if (flags)
  1512.             flags.value = row.flags;
  1513.  
  1514.         if (row.time_created) {
  1515.             item.setProperty("CREATED", newDateTime(row.time_created, "UTC"));
  1516.         }
  1517.  
  1518.         // This must be done last because the setting of any other property
  1519.         // after this would overwrite it again.
  1520.         if (row.last_modified) {
  1521.             item.setProperty("LAST-MODIFIED", newDateTime(row.last_modified, "UTC"));
  1522.         }
  1523.     },
  1524.  
  1525.     getEventFromRow: function (row, flags) {
  1526.         var item = createEvent();
  1527.  
  1528.         if (row.event_start)
  1529.             item.startDate = newDateTime(row.event_start, row.event_start_tz);
  1530.         if (row.event_end)
  1531.             item.endDate = newDateTime(row.event_end, row.event_end_tz);
  1532.         if (row.event_stamp)
  1533.             item.setProperty("DTSTAMP", newDateTime(row.event_stamp, "UTC"));
  1534.         if ((row.flags & CAL_ITEM_FLAG_EVENT_ALLDAY) != 0) {
  1535.             item.startDate.isDate = true;
  1536.             item.endDate.isDate = true;
  1537.         }
  1538.  
  1539.         // This must be done last to keep the modification time intact.
  1540.         this.getItemBaseFromRow (row, flags, item);
  1541.  
  1542.         return item;
  1543.     },
  1544.  
  1545.     getTodoFromRow: function (row, flags) {
  1546.         var item = createTodo();
  1547.  
  1548.         if (row.todo_entry)
  1549.             item.entryDate = newDateTime(row.todo_entry, row.todo_entry_tz);
  1550.         if (row.todo_due)
  1551.             item.dueDate = newDateTime(row.todo_due, row.todo_due_tz);
  1552.         if (row.todo_completed)
  1553.             item.completedDate = newDateTime(row.todo_completed, row.todo_completed_tz);
  1554.         if (row.todo_complete)
  1555.             item.percentComplete = row.todo_complete;
  1556.  
  1557.         // This must be done last to keep the modification time intact.
  1558.         this.getItemBaseFromRow (row, flags, item);
  1559.  
  1560.         return item;
  1561.     },
  1562.  
  1563.     // after we get the base item, we need to check if we need to pull in
  1564.     // any extra data from other tables.  We do that here.
  1565.  
  1566.     // note that we use mDBTwo for this, so this can be run while a
  1567.     // select is executing; don't use any statements that aren't
  1568.     // against mDBTwo in here!
  1569.     
  1570.     getAdditionalDataForItem: function (item, flags) {
  1571.         // This is needed to keep the modification time intact.
  1572.         var savedLastModifiedTime = item.lastModifiedTime;
  1573.  
  1574.         if (flags & CAL_ITEM_FLAG_HAS_ATTENDEES) {
  1575.             var selectItem = null;
  1576.             if (item.recurrenceId == null)
  1577.                 selectItem = this.mSelectAttendeesForItem;
  1578.             else {
  1579.                 selectItem = this.mSelectAttendeesForItemWithRecurrenceId;
  1580.                 this.setDateParamHelper(selectItem.params, "recurrence_id", item.recurrenceId);
  1581.             }
  1582.  
  1583.             selectItem.params.item_id = item.id;
  1584.  
  1585.             while (selectItem.step()) {
  1586.                 var attendee = this.getAttendeeFromRow(selectItem.row);
  1587.                 item.addAttendee(attendee);
  1588.             }
  1589.             selectItem.reset();
  1590.         }
  1591.  
  1592.         var row;
  1593.         if (flags & CAL_ITEM_FLAG_HAS_PROPERTIES) {
  1594.             var selectItem = null;
  1595.             if (item.recurrenceId == null)
  1596.                 selectItem = this.mSelectPropertiesForItem;
  1597.             else {
  1598.                 selectItem = this.mSelectPropertiesForItemWithRecurrenceId;
  1599.                 this.setDateParamHelper(selectItem.params, "recurrence_id", item.recurrenceId);
  1600.             }
  1601.                 
  1602.             selectItem.params.item_id = item.id;
  1603.             
  1604.             while (selectItem.step()) {
  1605.                 row = selectItem.row;
  1606.                 item.setProperty (row.key, row.value);
  1607.             }
  1608.             selectItem.reset();
  1609.         }
  1610.  
  1611.         var i;
  1612.         if (flags & CAL_ITEM_FLAG_HAS_RECURRENCE) {
  1613.             if (item.recurrenceId)
  1614.                 throw Components.results.NS_ERROR_UNEXPECTED;
  1615.  
  1616.             var rec = null;
  1617.  
  1618.             this.mSelectRecurrenceForItem.params.item_id = item.id;
  1619.             while (this.mSelectRecurrenceForItem.step()) {
  1620.                 row = this.mSelectRecurrenceForItem.row;
  1621.  
  1622.                 var ritem = null;
  1623.  
  1624.                 if (row.recur_type == null ||
  1625.                     row.recur_type == "x-dateset")
  1626.                 {
  1627.                     ritem = new CalRecurrenceDateSet();
  1628.  
  1629.                     var dates = row.dates.split(",");
  1630.                     for (i = 0; i < dates.length; i++) {
  1631.                         var date = textToDate(dates[i]);
  1632.                         ritem.addDate(date);
  1633.                     }
  1634.                 } else if (row.recur_type == "x-date") {
  1635.                     ritem = new CalRecurrenceDate();
  1636.                     var d = row.dates;
  1637.                     ritem.date = textToDate(d);
  1638.                 } else {
  1639.                     ritem = new CalRecurrenceRule();
  1640.  
  1641.                     ritem.type = row.recur_type;
  1642.                     if (row.count) {
  1643.                         try {
  1644.                             ritem.count = row.count;
  1645.                         } catch(exc) {
  1646.                         }
  1647.                     } else {
  1648.                         if (row.end_date)
  1649.                             ritem.endDate = newDateTime(row.end_date);
  1650.                         else
  1651.                             ritem.endDate = null;
  1652.                     }
  1653.                     try {
  1654.                         ritem.interval = row.interval;
  1655.                     } catch(exc) {
  1656.                     }
  1657.  
  1658.                     var rtypes = ["second",
  1659.                                   "minute",
  1660.                                   "hour",
  1661.                                   "day",
  1662.                                   "monthday",
  1663.                                   "yearday",
  1664.                                   "weekno",
  1665.                                   "month",
  1666.                                   "setpos"];
  1667.  
  1668.                     for (i = 0; i < rtypes.length; i++) {
  1669.                         var comp = "BY" + rtypes[i].toUpperCase();
  1670.                         if (row[rtypes[i]]) {
  1671.                             var rstr = row[rtypes[i]].toString().split(",");
  1672.                             var rarray = [];
  1673.                             for (var j = 0; j < rstr.length; j++) {
  1674.                                 rarray[j] = parseInt(rstr[j]);
  1675.                             }
  1676.  
  1677.                             ritem.setComponent (comp, rarray.length, rarray);
  1678.                         }
  1679.                     }
  1680.                 }
  1681.  
  1682.                 if (row.is_negative)
  1683.                     ritem.isNegative = true;
  1684.                 if (rec == null) {
  1685.                     rec = new CalRecurrenceInfo();
  1686.                     rec.item = item;
  1687.                 }
  1688.                 rec.appendRecurrenceItem(ritem);
  1689.             }
  1690.  
  1691.             if (rec == null) {
  1692.                 dump ("XXXX Expected to find recurrence, but got no items!\n");
  1693.             }
  1694.             item.recurrenceInfo = rec;
  1695.  
  1696.             this.mSelectRecurrenceForItem.reset();
  1697.         }
  1698.  
  1699.         if (flags & CAL_ITEM_FLAG_HAS_EXCEPTIONS) {
  1700.             if (item.recurrenceId)
  1701.                 throw Components.results.NS_ERROR_UNEXPECTED;
  1702.  
  1703.             var rec = item.recurrenceInfo;
  1704.  
  1705.             var exceptions = [];
  1706.  
  1707.             if (item instanceof Components.interfaces.calIEvent) {
  1708.                 this.mSelectEventExceptions.params.id = item.id;
  1709.                 while (this.mSelectEventExceptions.step()) {
  1710.                     var row = this.mSelectEventExceptions.row;
  1711.                     var flags = {};
  1712.                     var exc = this.getEventFromRow(row, flags);
  1713.                     exceptions.push({item: exc, flags: flags.value});
  1714.                 }
  1715.                 this.mSelectEventExceptions.reset();
  1716.             } else if (item instanceof Components.interfaces.calITodo) {
  1717.                 this.mSelectTodoExceptions.params.id = item.id;
  1718.                 while (this.mSelectTodoExceptions.step()) {
  1719.                     var row = this.mSelectTodoExceptions.row;
  1720.                     var flags = {};
  1721.                     var exc = this.getTodoFromRow(row, flags);
  1722.                     
  1723.                     exceptions.push({item: exc, flags: flags.value});
  1724.                 }
  1725.                 this.mSelectTodoExceptions.reset();
  1726.             } else {
  1727.                 throw Components.results.NS_ERROR_UNEXPECTED;
  1728.             }
  1729.  
  1730.             for each (var exc in exceptions) {
  1731.                 this.getAdditionalDataForItem(exc.item, exc.flags);
  1732.                 exc.item.parentItem = item;
  1733.                 rec.modifyException(exc.item);
  1734.             }
  1735.         }
  1736.  
  1737.         // Restore the saved modification time
  1738.         item.setProperty("LAST-MODIFIED", savedLastModifiedTime);
  1739.     },
  1740.  
  1741.     getAttendeeFromRow: function (row) {
  1742.         var a = CalAttendee();
  1743.  
  1744.         a.id = row.attendee_id;
  1745.         a.commonName = row.common_name;
  1746.         a.rsvp = (row.rsvp != 0);
  1747.         a.role = row.role;
  1748.         a.participationStatus = row.status;
  1749.         a.userType = row.type;
  1750.  
  1751.         return a;
  1752.     },
  1753.  
  1754.     //
  1755.     // get item from db or from cache with given iid
  1756.     //
  1757.     getItemById: function (aID) {
  1758.         // cached?
  1759.         if (this.mItemCache[aID] != null)
  1760.             return this.mItemCache[aID];
  1761.  
  1762.         var retval = null;
  1763.  
  1764.         // not cached; need to read from the db
  1765.         var flags = {};
  1766.         var item = null;
  1767.  
  1768.         // try events first
  1769.         this.mSelectEvent.params.id = aID;
  1770.         if (this.mSelectEvent.step())
  1771.             item = this.getEventFromRow(this.mSelectEvent.row, flags);
  1772.         this.mSelectEvent.reset();
  1773.  
  1774.         // try todo if event fails
  1775.         if (!item) {
  1776.             this.mSelectTodo.params.id = aID;
  1777.             if (this.mSelectTodo.step())
  1778.                 item = this.getTodoFromRow(this.mSelectTodo.row, flags);
  1779.             this.mSelectTodo.reset();
  1780.         }
  1781.  
  1782.         // bail if still not found
  1783.         if (!item)
  1784.             return null;
  1785.  
  1786.         this.getAdditionalDataForItem(item, flags.value);
  1787.  
  1788.         item.makeImmutable();
  1789.  
  1790.         // cache it
  1791.         this.mItemCache[aID] = item;
  1792.  
  1793.         return item;
  1794.     },
  1795.  
  1796.     //
  1797.     // database writing functions
  1798.     //
  1799.  
  1800.     setDateParamHelper: function (params, entryname, cdt) {
  1801.         if (cdt) {
  1802.             params[entryname] = cdt.nativeTime;
  1803.             params[entryname + "_tz"] = cdt.timezone;
  1804.         } else {
  1805.             params[entryname] = null;
  1806.             params[entryname + "_tz"] = null;
  1807.         }
  1808.     },
  1809.  
  1810.     flushItem: function (item, olditem) {
  1811.         this.mDB.beginTransaction();
  1812.         try {
  1813.             this.writeItem(item, olditem);
  1814.             this.mDB.commitTransaction();
  1815.         } catch (e) {
  1816.             dump("flushItem DB error: " + this.mDB.lastErrorString + "\n");
  1817.             Components.utils.reportError("flushItem DB error: " +
  1818.                                          this.mDB.lastErrorString);
  1819.             this.mDB.rollbackTransaction();
  1820.             throw e;
  1821.         }
  1822.  
  1823.         this.mItemCache[item.id] = item;
  1824.     },
  1825.  
  1826.     //
  1827.     // Nuke olditem, if any
  1828.     //
  1829.  
  1830.     deleteOldItem: function (item, olditem) {
  1831.         if (olditem) {
  1832.             var oldItemDeleteStmt;
  1833.             if (olditem instanceof Components.interfaces.calIEvent)
  1834.                 oldItemDeleteStmt = this.mDeleteEvent;
  1835.             else if (olditem instanceof Components.interfaces.calITodo)
  1836.                 oldItemDeleteStmt = this.mDeleteTodo;
  1837.  
  1838.             oldItemDeleteStmt.params.id = olditem.id;
  1839.             this.mDeleteAttendees.params.item_id = olditem.id;
  1840.             this.mDeleteProperties.params.item_id = olditem.id;
  1841.             this.mDeleteRecurrence.params.item_id = olditem.id;
  1842.  
  1843.             this.mDeleteRecurrence.execute();
  1844.             this.mDeleteProperties.execute();
  1845.             this.mDeleteAttendees.execute();
  1846.             oldItemDeleteStmt.execute();
  1847.         }
  1848.     },
  1849.  
  1850.     //
  1851.     // The write* functions execute the database bits
  1852.     // to write the given item type.  They're to return
  1853.     // any bits they want or'd into flags, which will be passed
  1854.     // to writeEvent/writeTodo to actually do the writing.
  1855.     //
  1856.  
  1857.     writeItem: function (item, olditem) {
  1858.         var flags = 0;
  1859.  
  1860.         this.deleteOldItem(item, olditem);
  1861.  
  1862.         flags |= this.writeAttendees(item, olditem);
  1863.         flags |= this.writeRecurrence(item, olditem);
  1864.         flags |= this.writeProperties(item, olditem);
  1865.         flags |= this.writeAttachments(item, olditem);
  1866.  
  1867.         if (item instanceof Components.interfaces.calIEvent)
  1868.             this.writeEvent(item, olditem, flags);
  1869.         else if (item instanceof Components.interfaces.calITodo)
  1870.             this.writeTodo(item, olditem, flags);
  1871.         else
  1872.             throw Components.results.NS_ERROR_UNEXPECTED;
  1873.     },
  1874.  
  1875.     writeEvent: function (item, olditem, flags) {
  1876.         var ip = this.mInsertEvent.params;
  1877.         this.setupItemBaseParams(item, olditem,ip);
  1878.  
  1879.         var tmp;
  1880.  
  1881.         tmp = item.getUnproxiedProperty("DTSTART");
  1882.         //if (tmp instanceof Components.interfaces.calIDateTime) {}
  1883.         this.setDateParamHelper(ip, "event_start", tmp);
  1884.         tmp = item.getUnproxiedProperty("DTEND");
  1885.         //if (tmp instanceof Components.interfaces.calIDateTime) {}
  1886.         this.setDateParamHelper(ip, "event_end", tmp);
  1887.  
  1888.         if (item.startDate.isDate)
  1889.             flags |= CAL_ITEM_FLAG_EVENT_ALLDAY;
  1890.  
  1891.         ip.flags = flags;
  1892.  
  1893.         this.mInsertEvent.execute();
  1894.     },
  1895.  
  1896.     writeTodo: function (item, olditem, flags) {
  1897.         var ip = this.mInsertTodo.params;
  1898.  
  1899.         this.setupItemBaseParams(item, olditem,ip);
  1900.  
  1901.         this.setDateParamHelper(ip, "todo_entry", item.getUnproxiedProperty("DTSTART"));
  1902.         this.setDateParamHelper(ip, "todo_due", item.getUnproxiedProperty("DUE"));
  1903.         this.setDateParamHelper(ip, "todo_completed", item.getUnproxiedProperty("COMPLETED"));
  1904.  
  1905.         ip.todo_complete = item.getUnproxiedProperty("PERCENT-COMPLETED");
  1906.  
  1907.         ip.flags = flags;
  1908.  
  1909.         this.mInsertTodo.execute();
  1910.     },
  1911.  
  1912.     setupItemBaseParams: function (item, olditem, ip) {
  1913.         ip.cal_id = this.mCalId;
  1914.         ip.id = item.id;
  1915.  
  1916.         if (item.recurrenceId)
  1917.             this.setDateParamHelper(ip, "recurrence_id", item.recurrenceId);
  1918.  
  1919.         var tmp;
  1920.  
  1921.         if ((tmp = item.getUnproxiedProperty("CREATED")))
  1922.             ip.time_created = tmp.nativeTime;
  1923.         if ((tmp = item.getUnproxiedProperty("LAST-MODIFIED")))
  1924.             ip.last_modified = tmp.nativeTime;
  1925.  
  1926.         ip.title = item.getUnproxiedProperty("SUMMARY");
  1927.         ip.priority = item.getUnproxiedProperty("PRIORITY");
  1928.         ip.privacy = item.getUnproxiedProperty("CLASS");
  1929.         ip.ical_status = item.getUnproxiedProperty("STATUS");
  1930.  
  1931.         if (!item.parentItem)
  1932.             ip.event_stamp = item.stampTime.nativeTime;
  1933.  
  1934.         if (item.alarmOffset) {
  1935.             ip.alarm_offset = item.alarmOffset.inSeconds;
  1936.             ip.alarm_related = item.alarmRelated;
  1937.             if (item.alarmLastAck) {
  1938.                 ip.alarm_last_ack = item.alarmLastAck.nativeTime;
  1939.             }
  1940.         }
  1941.     },
  1942.  
  1943.     writeAttendees: function (item, olditem) {
  1944.         // XXX how does this work for proxy stuffs?
  1945.         var attendees = item.getAttendees({});
  1946.         if (attendees && attendees.length > 0) {
  1947.             for each (var att in attendees) {
  1948.                 var ap = this.mInsertAttendee.params;
  1949.                 ap.item_id = item.id;
  1950.                 this.setDateParamHelper(ap, "recurrence_id", item.recurrenceId);
  1951.                 ap.attendee_id = att.id;
  1952.                 ap.common_name = att.commonName;
  1953.                 ap.rsvp = att.rsvp;
  1954.                 ap.role = att.role;
  1955.                 ap.status = att.participationStatus;
  1956.                 ap.type = att.userType;
  1957.  
  1958.                 this.mInsertAttendee.execute();
  1959.             }
  1960.  
  1961.             return CAL_ITEM_FLAG_HAS_ATTENDEES;
  1962.         }
  1963.  
  1964.         return 0;
  1965.     },
  1966.  
  1967.     writeProperties: function (item, olditem) {
  1968.         var ret = 0;
  1969.         var propEnumerator = item.unproxiedPropertyEnumerator;
  1970.         while (propEnumerator.hasMoreElements()) {
  1971.             ret = CAL_ITEM_FLAG_HAS_PROPERTIES;
  1972.  
  1973.             var prop = propEnumerator.getNext().QueryInterface(Components.interfaces.nsIProperty);
  1974.  
  1975.             if (item.isPropertyPromoted(prop.name))
  1976.                 continue;
  1977.  
  1978.             var pp = this.mInsertProperty.params;
  1979.  
  1980.             pp.key = prop.name;
  1981.             var pval = prop.value;
  1982.             if (pval instanceof Components.interfaces.calIDateTime) {
  1983.                 pp.value = pval.nativeTime;
  1984.             } else {
  1985.                 pp.value = pval;
  1986.             }
  1987.             pp.item_id = item.id;
  1988.             this.setDateParamHelper(pp, "recurrence_id", item.recurrenceId);
  1989.  
  1990.             this.mInsertProperty.execute();
  1991.         }
  1992.  
  1993.         return ret;
  1994.     },
  1995.  
  1996.     writeRecurrence: function (item, olditem) {
  1997.         var flags = 0;
  1998.  
  1999.         var rec = item.recurrenceInfo;
  2000.         if (rec) {
  2001.             flags = CAL_ITEM_FLAG_HAS_RECURRENCE;
  2002.             var ritems = rec.getRecurrenceItems ({});
  2003.             for (i in ritems) {
  2004.                 var ritem = ritems[i];
  2005.                 ap = this.mInsertRecurrence.params;
  2006.                 ap.item_id = item.id;
  2007.                 ap.recur_index = i;
  2008.                 ap.is_negative = ritem.isNegative;
  2009.                 if (ritem instanceof kCalIRecurrenceDate) {
  2010.                     ritem = ritem.QueryInterface(kCalIRecurrenceDate);
  2011.                     ap.recur_type = "x-date";
  2012.                     ap.dates = dateToText(ritem.date);
  2013.  
  2014.                 } else if (ritem instanceof kCalIRecurrenceDateSet) {
  2015.                     ritem = ritem.QueryInterface(kCalIRecurrenceDateSet);
  2016.                     ap.recur_type = "x-dateset";
  2017.  
  2018.                     var rdates = ritem.getDates({});
  2019.                     var datestr = "";
  2020.                     for (j in rdates) {
  2021.                         if (j != 0)
  2022.                             datestr += ",";
  2023.  
  2024.                         datestr += dateToText(rdates[j]);
  2025.                     }
  2026.  
  2027.                     ap.dates = datestr;
  2028.  
  2029.                 } else if (ritem instanceof kCalIRecurrenceRule) {
  2030.                     ritem = ritem.QueryInterface(kCalIRecurrenceRule);
  2031.                     ap.recur_type = ritem.type;
  2032.  
  2033.                     if (ritem.isByCount)
  2034.                         ap.count = ritem.count;
  2035.                     else
  2036.                         ap.end_date = ritem.endDate ? ritem.endDate.nativeTime : null;
  2037.  
  2038.                     ap.interval = ritem.interval;
  2039.  
  2040.                     var rtypes = ["second",
  2041.                                   "minute",
  2042.                                   "hour",
  2043.                                   "day",
  2044.                                   "monthday",
  2045.                                   "yearday",
  2046.                                   "weekno",
  2047.                                   "month",
  2048.                                   "setpos"];
  2049.                     for (j = 0; j < rtypes.length; j++) {
  2050.                         var comp = "BY" + rtypes[j].toUpperCase();
  2051.                         var comps = ritem.getComponent(comp, {});
  2052.                         if (comps && comps.length > 0) {
  2053.                             var compstr = comps.join(",");
  2054.                             ap[rtypes[j]] = compstr;
  2055.                         }
  2056.                     }
  2057.                 } else {
  2058.                     dump ("##### Don't know how to serialize recurrence item " + ritem + "!\n");
  2059.                 }
  2060.  
  2061.                 this.mInsertRecurrence.execute();
  2062.             }
  2063.  
  2064.             var exceptions = rec.getExceptionIds ({});
  2065.             if (exceptions.length > 0) {
  2066.                 flags |= CAL_ITEM_FLAG_HAS_EXCEPTIONS;
  2067.  
  2068.                 // we need to serialize each exid as a separate
  2069.                 // event/todo; setupItemBase will handle
  2070.                 // writing the recurrenceId for us
  2071.                 for each (exid in exceptions) {
  2072.                     var ex = rec.getExceptionFor(exid, false);
  2073.                     if (!ex)
  2074.                         throw Components.results.NS_ERROR_UNEXPECTED;
  2075.                     this.writeItem(ex, null);
  2076.                 }
  2077.             }
  2078.         }
  2079.  
  2080.         return flags;
  2081.     },
  2082.  
  2083.     writeAttachments: function (item, olditem) {
  2084.         // XXX write me?
  2085.         return 0;
  2086.     },
  2087.  
  2088.     //
  2089.     // delete the item with the given uid
  2090.     //
  2091.     deleteItemById: function (aID) {
  2092.         this.mDB.beginTransaction();
  2093.         try {
  2094.             this.mDeleteAttendees(aID);
  2095.             this.mDeleteProperties(aID);
  2096.             this.mDeleteRecurrence(aID);
  2097.             this.mDeleteEvent(aID);
  2098.             this.mDeleteTodo(aID);
  2099.             this.mDB.commitTransaction();
  2100.         } catch (e) {
  2101.             Components.utils.reportError("deleteItemById DB error: " + 
  2102.                                          this.mDB.lastErrorString);
  2103.             this.mDB.rollbackTransaction();
  2104.             throw e;
  2105.         }
  2106.  
  2107.         delete this.mItemCache[aID];
  2108.     }
  2109. }
  2110.  
  2111.  
  2112. /****
  2113.  **** module registration
  2114.  ****/
  2115.  
  2116. var calStorageCalendarModule = {
  2117.     mCID: Components.ID("{b3eaa1c4-5dfe-4c0a-b62a-b3a514218461}"),
  2118.     mContractID: "@mozilla.org/calendar/calendar;1?type=storage",
  2119.  
  2120.     mUtilsLoaded: false,
  2121.     loadUtils: function storageLoadUtils() {
  2122.         if (this.mUtilsLoaded)
  2123.             return;
  2124.  
  2125.         const jssslContractID = "@mozilla.org/moz/jssubscript-loader;1";
  2126.         const jssslIID = Components.interfaces.mozIJSSubScriptLoader;
  2127.  
  2128.         const iosvcContractID = "@mozilla.org/network/io-service;1";
  2129.         const iosvcIID = Components.interfaces.nsIIOService;
  2130.  
  2131.         var loader = Components.classes[jssslContractID].getService(jssslIID);
  2132.         var iosvc = Components.classes[iosvcContractID].getService(iosvcIID);
  2133.  
  2134.         // Note that unintuitively, __LOCATION__.parent == .
  2135.         // We expect to find utils in ./../js
  2136.         var appdir = __LOCATION__.parent.parent;
  2137.         appdir.append("js");
  2138.         var scriptName = "calUtils.js";
  2139.  
  2140.         var f = appdir.clone();
  2141.         f.append(scriptName);
  2142.  
  2143.         try {
  2144.             var fileurl = iosvc.newFileURI(f);
  2145.             loader.loadSubScript(fileurl.spec, this.__parent__.__parent__);
  2146.         } catch (e) {
  2147.             dump("Error while loading " + fileurl.spec + "\n");
  2148.             throw e;
  2149.         }
  2150.  
  2151.         this.mUtilsLoaded = true;
  2152.     },
  2153.     
  2154.     registerSelf: function (compMgr, fileSpec, location, type) {
  2155.         compMgr = compMgr.QueryInterface(Components.interfaces.nsIComponentRegistrar);
  2156.         compMgr.registerFactoryLocation(this.mCID,
  2157.                                         "Calendar mozStorage/SQL back-end",
  2158.                                         this.mContractID,
  2159.                                         fileSpec,
  2160.                                         location,
  2161.                                         type);
  2162.     },
  2163.  
  2164.     getClassObject: function (compMgr, cid, iid) {
  2165.         if (!cid.equals(this.mCID))
  2166.             throw Components.results.NS_ERROR_NO_INTERFACE;
  2167.  
  2168.         if (!iid.equals(Components.interfaces.nsIFactory))
  2169.             throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
  2170.  
  2171.         if (!kStorageServiceIID)
  2172.             throw Components.results.NS_ERROR_NOT_INITIALIZED;
  2173.  
  2174.         this.loadUtils();
  2175.  
  2176.         if (!CalAttendee) {
  2177.             initCalStorageCalendarComponent();
  2178.         }
  2179.  
  2180.         return this.mFactory;
  2181.     },
  2182.  
  2183.     mFactory: {
  2184.         createInstance: function (outer, iid) {
  2185.             if (outer != null)
  2186.                 throw Components.results.NS_ERROR_NO_AGGREGATION;
  2187.             return (new calStorageCalendar()).QueryInterface(iid);
  2188.         }
  2189.     },
  2190.  
  2191.     canUnload: function(compMgr) {
  2192.         return true;
  2193.     }
  2194. };
  2195.  
  2196. function NSGetModule(compMgr, fileSpec) {
  2197.     return calStorageCalendarModule;
  2198. }
  2199.  
  2200.  
  2201.  
  2202. //
  2203. // sqlTables generated from schema.sql via makejsschema.pl
  2204. //
  2205.  
  2206. var sqlTables = {
  2207.   cal_calendar_schema_version:
  2208.     "    version    INTEGER" +
  2209.     "",
  2210.  
  2211.   cal_events:
  2212.     /*     REFERENCES cal_calendars.id, */
  2213.     "    cal_id        INTEGER, " +
  2214.     /*  ItemBase bits */
  2215.     "    id        TEXT," +
  2216.     "    time_created    INTEGER," +
  2217.     "    last_modified    INTEGER," +
  2218.     "    title        TEXT," +
  2219.     "    priority    INTEGER," +
  2220.     "    privacy        TEXT," +
  2221.     "    ical_status    TEXT," +
  2222.     "    recurrence_id    INTEGER," +
  2223.     "    recurrence_id_tz    TEXT," +
  2224.     /*  CAL_ITEM_FLAG_PRIVATE = 1 */
  2225.     /*  CAL_ITEM_FLAG_HAS_ATTENDEES = 2 */
  2226.     /*  CAL_ITEM_FLAG_HAS_PROPERTIES = 4 */
  2227.     /*  CAL_ITEM_FLAG_EVENT_ALLDAY = 8 */
  2228.     /*  CAL_ITEM_FLAG_HAS_RECURRENCE = 16 */
  2229.     /*  CAL_ITEM_FLAG_HAS_EXCEPTIONS = 32 */
  2230.     "    flags        INTEGER," +
  2231.     /*  Event bits */
  2232.     "    event_start    INTEGER," +
  2233.     "    event_start_tz    TEXT," +
  2234.     "    event_end    INTEGER," +
  2235.     "    event_end_tz    TEXT," +
  2236.     "    event_stamp    INTEGER," +
  2237.     /*  alarm time */
  2238.     "    alarm_time    INTEGER," +
  2239.     "    alarm_time_tz    TEXT," +
  2240.     "    alarm_offset    INTEGER," +
  2241.     "    alarm_related    INTEGER," +
  2242.     "    alarm_last_ack    INTEGER" +
  2243.     "",
  2244.  
  2245.   cal_todos:
  2246.     /*     REFERENCES cal_calendars.id, */
  2247.     "    cal_id        INTEGER, " +
  2248.     /*  ItemBase bits */
  2249.     "    id        TEXT," +
  2250.     "    time_created    INTEGER," +
  2251.     "    last_modified    INTEGER," +
  2252.     "    title        TEXT," +
  2253.     "    priority    INTEGER," +
  2254.     "    privacy        TEXT," +
  2255.     "    ical_status    TEXT," +
  2256.     "    recurrence_id    INTEGER," +
  2257.     "    recurrence_id_tz    TEXT," +
  2258.     /*  CAL_ITEM_FLAG_PRIVATE = 1 */
  2259.     /*  CAL_ITEM_FLAG_HAS_ATTENDEES = 2 */
  2260.     /*  CAL_ITEM_FLAG_HAS_PROPERTIES = 4 */
  2261.     /*  CAL_ITEM_FLAG_EVENT_ALLDAY = 8 */
  2262.     /*  CAL_ITEM_FLAG_HAS_RECURRENCE = 16 */
  2263.     /*  CAL_ITEM_FLAG_HAS_EXCEPTIONS = 32 */
  2264.     "    flags        INTEGER," +
  2265.     /*  Todo bits */
  2266.     /*  date the todo is to be displayed */
  2267.     "    todo_entry    INTEGER," +
  2268.     "    todo_entry_tz    TEXT," +
  2269.     /*  date the todo is due */
  2270.     "    todo_due    INTEGER," +
  2271.     "    todo_due_tz    TEXT," +
  2272.     /*  date the todo is completed */
  2273.     "    todo_completed    INTEGER," +
  2274.     "    todo_completed_tz TEXT," +
  2275.     /*  percent the todo is complete (0-100) */
  2276.     "    todo_complete    INTEGER," +
  2277.     /*  alarm time */
  2278.     "    alarm_time    INTEGER," +
  2279.     "    alarm_time_tz    TEXT," +
  2280.     "    alarm_offset    INTEGER," +
  2281.     "    alarm_related    INTEGER," +
  2282.     "    alarm_last_ack    INTEGER" +
  2283.     "",
  2284.  
  2285.   cal_attendees:
  2286.     "    item_id         TEXT," +
  2287.     "    recurrence_id    INTEGER," +
  2288.     "    recurrence_id_tz    TEXT," +
  2289.     "    attendee_id    TEXT," +
  2290.     "    common_name    TEXT," +
  2291.     "    rsvp        INTEGER," +
  2292.     "    role        TEXT," +
  2293.     "    status        TEXT," +
  2294.     "    type        TEXT" +
  2295.     "",
  2296.  
  2297.   cal_recurrence:
  2298.     "    item_id        TEXT," +
  2299.     /*  the index in the recurrence array of this thing */
  2300.     "    recur_index    INTEGER, " +
  2301.     /*  values from calIRecurrenceInfo; if null, date-based. */
  2302.     "    recur_type    TEXT, " +
  2303.     "    is_negative    BOOLEAN," +
  2304.     /*  */
  2305.     /*  these are for date-based recurrence */
  2306.     /*  */
  2307.     /*  comma-separated list of dates */
  2308.     "    dates        TEXT," +
  2309.     /*  */
  2310.     /*  these are for rule-based recurrence */
  2311.     /*  */
  2312.     "    count        INTEGER," +
  2313.     "    end_date    INTEGER," +
  2314.     "    interval    INTEGER," +
  2315.     /*  components, comma-separated list or null */
  2316.     "    second        TEXT," +
  2317.     "    minute        TEXT," +
  2318.     "    hour        TEXT," +
  2319.     "    day        TEXT," +
  2320.     "    monthday    TEXT," +
  2321.     "    yearday        TEXT," +
  2322.     "    weekno        TEXT," +
  2323.     "    month        TEXT," +
  2324.     "    setpos        TEXT" +
  2325.     "",
  2326.  
  2327.   cal_properties:
  2328.     "    item_id        TEXT," +
  2329.     "    recurrence_id    INTEGER," +
  2330.     "    recurrence_id_tz    TEXT," +
  2331.     "    key        TEXT," +
  2332.     "    value        BLOB" +
  2333.     "",
  2334.  
  2335. };
  2336.